Created
July 19, 2023 21:07
-
-
Save juliandavidmr/e86815d7431d7e71a190a50e2bc510e3 to your computer and use it in GitHub Desktop.
Create Amplitude experiment in React using Exposure Event
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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