Add our own cache in front of Statsig (#3604)
parent
02becdf449
commit
41b5b5b283
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue