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