Skip to content

Instantly share code, notes, and snippets.

@juliandavidmr
Created July 19, 2023 21:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save juliandavidmr/e86815d7431d7e71a190a50e2bc510e3 to your computer and use it in GitHub Desktop.
Save juliandavidmr/e86815d7431d7e71a190a50e2bc510e3 to your computer and use it in GitHub Desktop.
Create Amplitude experiment in React using Exposure Event
import { useCallback, useEffect, useState } from 'react';
import { Experiment, ExperimentClient } from '@amplitude/experiment-js-client';
import {
AmplitudeExperimentContext,
type AmplitudeExperimentContextValue,
} from './AmplitudeExperimentContext';
export interface AmplitudeExperimentProviderProps {
envExperimentKey: string;
debugMode?: boolean;
}
const AmplitudeExperimentProvider: React.FC<
React.PropsWithChildren<AmplitudeExperimentProviderProps>
> = ({ envExperimentKey, debugMode: debug = false, children }) => {
const [loading, setLoading] = useState(true);
const [loaded, setLoaded] = useState(false);
const startExperiment = useCallback(async (experiment: ExperimentClient) => {
try {
setLoading(true);
const experimentClient = await experiment.fetch();
setLoaded(true);
return experimentClient;
} catch (e) {
console.error(e);
setLoaded(false);
} finally {
setLoading(false);
}
}, []);
const getExperiment = useCallback(async (): Promise<
ExperimentClient | undefined
> => {
if (typeof window === 'undefined' || !envExperimentKey) {
return Promise.reject(new Error('envExperimentKey is required'));
}
const internExperiment = Experiment.initializeWithAmplitudeAnalytics(
envExperimentKey,
{
debug,
},
);
const experimentClient = await startExperiment(internExperiment);
if (!experimentClient) {
return Promise.reject(new Error('ExperimentClient is undefined'));
}
if (debug) {
console.info('ExperimentClient initialized');
}
return experimentClient;
}, [envExperimentKey, debug, startExperiment]);
const isVariantActive = async (variantId: string, variantValue?: string) => {
const experiment = await getExperiment();
if (typeof experiment !== 'undefined') {
const variantObject = experiment.variant(variantId);
if (variantValue) {
return variantObject.value === variantValue;
}
return variantObject.value !== 'control';
}
return false;
};
const isControlActive = (variantId: string, defaultControl = 'control') =>
isVariantActive(variantId, defaultControl);
async function getPayloadVariant<T>(variantId: string): Promise<T | null> {
const experiment = await getExperiment();
if (experiment) {
const variantObject = experiment.variant(variantId);
return variantObject.payload as T;
}
return null;
}
useEffect(() => {
const timerLoading = setTimeout(() => {
setLoading(false);
}, 2000);
return () => clearTimeout(timerLoading);
}, []);
const value: AmplitudeExperimentContextValue = {
getExperiment,
loading,
loaded,
isVariantActive,
isControlActive,
getPayloadVariant,
};
return (
<AmplitudeExperimentContext.Provider value={value}>
{children}
</AmplitudeExperimentContext.Provider>
);
};
export { AmplitudeExperimentContext, AmplitudeExperimentProvider };
import { createContext } from 'react';
import type { ExperimentClient } from '@amplitude/experiment-js-client';
export interface AmplitudeExperimentContextValue {
getExperiment: () => Promise<ExperimentClient | undefined>;
isVariantActive: (
variantId: string,
variantValue?: string,
) => Promise<boolean>;
/**
* @description Returns true if the user is in the experiment with variant `control`.
*/
isControlActive: (
variantId: string,
defaultControl?: string,
) => Promise<boolean>;
getPayloadVariant: <T>(variantId: string) => Promise<T | null>;
loading: boolean;
loaded: boolean;
}
export const AmplitudeExperimentContext =
createContext<AmplitudeExperimentContextValue | null>(null);
import { useContext } from 'react';
import {
AmplitudeExperimentContext,
AmplitudeExperimentContextValue,
} from './AmplitudeExperimentContext';
const useAmplitudeExperiment = (): AmplitudeExperimentContextValue => {
const context = useContext(AmplitudeExperimentContext);
if (!context) {
throw new Error(
'useAmplitudeExperiment must be used within a AmplitudeExperimentProvider',
);
}
return context;
};
export default useAmplitudeExperiment;
import { useCallback, useState } from 'react';
const key = 'your-flag-key-here';
const useDemoAB = () => {
const [loading, setLoading] = useState(false);
const [currentVariant, setCurrentVariant] = useState<Variant | undefined>(undefined);
const [isControlActive, setIsControlActive] = useState<boolean>(false);
const { getExperiment } = useAmplitudeExperiment();
const isLoaded = currentVariant !== undefined;
const activateExperiment = useCallback(async () => {
setLoading(true);
try {
const experiment = await getExperiment();
if (experiment) {
experiment.exposure(key);
const currentVariant = experiment.variant(key);
const isControlActive = currentVariant.value === 'control';
setCurrentVariant(currentVariant);
setIsControlActive(isControlActive);
console.log('[[variants]]', currentVariant);
} else {
console.error('Experiment is not defined');
}
} catch (error) {
console.error(error);
}
setLoading(false);
}, [getExperiment]);
return {
loading,
isLoaded,
currentVariant,
isControlActive,
activateExperiment,
};
};
export default useDemoAB;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment