@devcycle/react-client-sdk
Version:
The DevCycle React SDK used for feature management.
225 lines (211 loc) • 8.38 kB
JavaScript
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
import { createContext, useContext, useState, useCallback, useEffect, useRef, forwardRef } from 'react';
import { initializeDevCycle } from '@devcycle/js-client-sdk';
import hoistNonReactStatics from 'hoist-non-react-statics';
const context = createContext(undefined);
const { Provider, Consumer } = context;
const initializedContext = createContext({
isInitialized: false,
});
const debugContextDefaults = {
showConditionalBorders: false,
borderColor: '#ff6347',
};
const debugContext = createContext(debugContextDefaults);
const initializeDevCycleClient = (sdkKey, user = { isAnonymous: true }, options) => {
if (options === null || options === void 0 ? void 0 : options.deferInitialization) {
return initializeDevCycle(sdkKey, {
sdkPlatform: 'react',
...options,
deferInitialization: true, // make typescript happy
bootstrapConfig: undefined,
});
}
return initializeDevCycle(sdkKey, user, {
sdkPlatform: 'react',
...options,
});
};
/**
* @deprecated Use the `useIsDevCycleInitialized` hook to block rendering of your application
* until SDK initialization is complete
*/
async function asyncWithDVCProvider(config) {
const { user, options } = config;
let sdkKey;
if ('sdkKey' in config) {
sdkKey = config.sdkKey;
}
else {
sdkKey = config.envKey;
}
if (!sdkKey) {
throw new Error('You must provide a sdkKey to asyncWithDVCProvider');
}
const client = initializeDevCycleClient(sdkKey, user, options);
await client.onClientInitialized();
return ({ children }) => {
return jsx(Provider, { value: { client }, children: children });
};
}
const useDevCycleClient = () => {
const dvcContext = useContext(context);
if (dvcContext === undefined)
throw new Error('useDevCycleClient must be used within DevCycleProvider');
// enforce the variable and custom data types provided by the user. These are for type-checking only.
return dvcContext.client;
};
/**
* @deprecated use useDevCycleClient instead
*/
const useDVCClient = useDevCycleClient;
const useVariable = (key, defaultValue) => {
const dvcContext = useContext(context);
const [_, forceRerender] = useState({});
const forceRerenderCallback = useCallback(() => forceRerender({}), []);
if (dvcContext === undefined)
throw new Error('useVariable must be used within DevCycleProvider');
useEffect(() => {
dvcContext.client.subscribe(`variableUpdated:${key}`, forceRerenderCallback);
return () => {
dvcContext.client.unsubscribe(`variableUpdated:${key}`, forceRerenderCallback);
};
}, [dvcContext, key, forceRerenderCallback]);
return dvcContext.client.variable(key, defaultValue);
};
const useVariableValue = (key, defaultValue) => {
return useVariable(key, defaultValue).value;
};
function DevCycleProvider(props) {
var _a, _b;
const { config } = props;
const { user, options } = config;
const [isInitialized, setIsInitialized] = useState(false);
let sdkKey;
if ('sdkKey' in config) {
sdkKey = config.sdkKey;
}
else {
sdkKey = config.envKey;
}
if (!sdkKey) {
throw new Error('You must provide a sdkKey to DevCycleProvider');
}
const clientRef = useRef();
const [_, forceRerender] = useState({});
if (clientRef.current === undefined) {
clientRef.current = initializeDevCycleClient(sdkKey, user, {
...options,
});
}
useEffect(() => {
if (clientRef.current === undefined) {
clientRef.current = initializeDevCycleClient(sdkKey, user, {
...options,
});
// react doesn't know the effect changed the ref, make sure it re-renders
forceRerender({});
}
clientRef.current
.onClientInitialized()
.then(() => {
setIsInitialized(true);
})
.catch(() => {
// set to true to unblock app load
console.log('Error initializing DevCycle.');
setIsInitialized(true);
});
return () => {
var _a;
void ((_a = clientRef.current) === null || _a === void 0 ? void 0 : _a.close());
clientRef.current = undefined;
};
}, [sdkKey, user, options]);
const mergedDebugOptions = Object.assign({}, debugContextDefaults, (_b = (_a = props.config.options) === null || _a === void 0 ? void 0 : _a.reactDebug) !== null && _b !== void 0 ? _b : {});
return (jsx(Provider, { value: { client: clientRef.current }, children: jsx(initializedContext.Provider, { value: {
isInitialized: isInitialized || clientRef.current.isInitialized,
}, children: jsx(debugContext.Provider, { value: mergedDebugOptions, children: props.children }) }) }));
}
/**
* @deprecated Use DevCycleProvider instead
*/
const DVCProvider = DevCycleProvider;
function withDevCycleProvider(config) {
return function (WrappedComponent) {
const HoistedComponent = forwardRef((props, ref) => {
return (jsx(DevCycleProvider, { config: config, children: jsx(WrappedComponent, { ...props, ref: ref }) }));
});
hoistNonReactStatics(HoistedComponent, WrappedComponent);
return HoistedComponent;
};
}
/**
* @deprecated Use withDevCycleProvider instead
*/
const withDVCProvider = withDevCycleProvider;
/**
*
* @deprecated Use the `useVariable` hook instead
*
*/
const useDVCVariable = (key, defaultValue) => {
return useVariable(key, defaultValue);
};
const useIsDevCycleInitialized = () => {
const context = useContext(initializedContext);
if (context === undefined)
throw new Error('useIsDevCycleInitialized must be used within DevCycleProvider');
return context.isInitialized;
};
/**
* @deprecated use useIsDevCycleInitialized instead
*/
const useIsDVCInitialized = useIsDevCycleInitialized;
const RenderIfEnabled = (props) => {
let targetValue;
let defaultValue;
if ('targetValue' in props) {
targetValue = props.targetValue;
defaultValue = props.defaultValue;
}
else {
targetValue = true;
defaultValue = false;
}
const variableValue = useVariableValue(props.variableKey, defaultValue);
const debugSettings = useContext(debugContext);
if (variableValue === targetValue) {
if (debugSettings.showConditionalBorders) {
return (jsxs("div", { style: {
border: `2px solid ${debugSettings.borderColor}`,
position: 'relative',
}, className: `devcycle-conditional-border devcycle-conditional-border-${props.variableKey}`, children: [jsxs("a", { style: {
position: 'absolute',
cursor: 'pointer',
right: '-2px',
top: '-2.5rem',
color: 'white',
fontSize: '1.5rem',
padding: '2px 5px',
backgroundColor: `${debugSettings.borderColor}`,
}, target: '_blank', href: `https://app.devcycle.com/r/variables/${props.variableKey}`, rel: "noreferrer", children: [props.variableKey, ": ", JSON.stringify(variableValue)] }), props.children] }));
}
return jsx(Fragment, { children: props.children });
}
return null;
};
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
const SwapComponents = (OldComponent, NewComponent, variableKey) => {
const DevCycleConditionalComponent = (props) => {
const variableValue = useVariableValue(variableKey, false);
if (variableValue) {
return jsx(NewComponent, { ...props });
}
else {
return jsx(OldComponent, { ...props });
}
};
return DevCycleConditionalComponent;
};
export { DVCProvider, DevCycleProvider, RenderIfEnabled, SwapComponents, asyncWithDVCProvider, useDVCClient, useDVCVariable, useDevCycleClient, useIsDVCInitialized, useIsDevCycleInitialized, useVariable, useVariableValue, withDVCProvider, withDevCycleProvider };