Add our own cache in front of Statsig (#3604)

zio/stable
dan 2024-04-18 15:28:24 +01:00 committed by GitHub
parent 02becdf449
commit 41b5b5b283
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 45 additions and 32 deletions

View File

@ -94,23 +94,30 @@ export function logEvent<E extends keyof LogEvents>(
}
}
// We roll our own cache in front of Statsig because it is a singleton
// and it's been difficult to get it to behave in a predictable way.
// Our own cache ensures consistent evaluation within a single session.
const GateCache = React.createContext<Map<string, boolean> | null>(null)
export function useGate(): (gateName: Gate) => boolean {
const cache = React.useRef<Map<Gate, boolean>>()
if (cache.current === undefined) {
cache.current = new Map()
const cache = React.useContext(GateCache)
if (!cache) {
throw Error('useGate() cannot be called outside StatsigProvider.')
}
const gate = React.useCallback((gateName: Gate): boolean => {
// TODO: Replace local cache with a proper session one.
const cachedValue = cache.current!.get(gateName)
if (cachedValue !== undefined) {
return cachedValue
}
const value = Statsig.initializeCalled()
? Statsig.checkGate(gateName)
: false
cache.current!.set(gateName, value)
return value
}, [])
const gate = React.useCallback(
(gateName: Gate): boolean => {
const cachedValue = cache.get(gateName)
if (cachedValue !== undefined) {
return cachedValue
}
const value = Statsig.initializeCalled()
? Statsig.checkGate(gateName)
: false
cache.set(gateName, value)
return value
},
[cache],
)
return gate
}
@ -154,16 +161,19 @@ AppState.addEventListener('change', (state: AppStateStatus) => {
export function Provider({children}: {children: React.ReactNode}) {
const {currentAccount} = useSession()
const currentStatsigUser = React.useMemo(
() => toStatsigUser(currentAccount?.did),
[currentAccount?.did],
)
const did = currentAccount?.did
const currentStatsigUser = React.useMemo(() => toStatsigUser(did), [did])
const [gateCache, setGateCache] = React.useState(() => new Map())
const [prevDid, setPrevDid] = React.useState(did)
if (did !== prevDid) {
setPrevDid(did)
setGateCache(new Map())
}
React.useEffect(() => {
function refresh() {
// Intentionally refetching the config using the JS SDK rather than React SDK
// so that the new config is stored in cache but isn't used during this session.
// It will kick in for the next reload.
// This will not affect the current session.
// Statsig will put the results into local storage and we'll pick it up on next load.
Statsig.updateUser(currentStatsigUser)
}
const id = setInterval(refresh, 3 * 60e3 /* 3 min */)
@ -171,15 +181,18 @@ export function Provider({children}: {children: React.ReactNode}) {
}, [currentStatsigUser])
return (
<StatsigProvider
sdkKey="client-SXJakO39w9vIhl3D44u8UupyzFl4oZ2qPIkjwcvuPsV"
mountKey={currentStatsigUser.userID}
user={currentStatsigUser}
// This isn't really blocking due to short initTimeoutMs above.
// However, it ensures `isLoading` is always `false`.
waitForInitialization={true}
options={statsigOptions}>
{children}
</StatsigProvider>
<GateCache.Provider value={gateCache}>
<StatsigProvider
key={did}
sdkKey="client-SXJakO39w9vIhl3D44u8UupyzFl4oZ2qPIkjwcvuPsV"
mountKey={currentStatsigUser.userID}
user={currentStatsigUser}
// This isn't really blocking due to short initTimeoutMs above.
// However, it ensures `isLoading` is always `false`.
waitForInitialization={true}
options={statsigOptions}>
{children}
</StatsigProvider>
</GateCache.Provider>
)
}