Open klaasman opened 1 year ago
what the heck? I'm guessing it's a problem if the server renders one thing, and the client renders another, but that's just the initial render! there's no reason to drag this across the app's entire lifecycle.
The Provider can do this more efficiently, ie.
client.haveFeatureFlags
client.isInitialRender / isHydrationRender
This is quite a frustrating gotcha, but easily solved with a custom hook:
export function useFeatureFlagEnabledWithCurrentValueAsDefault(flag: string): boolean | undefined {
const client = usePostHog()
const defaultFlagValue = client.isFeatureEnabled(flag) // this just locally retrieves the current value...
const [featureEnabled, setFeatureEnabled] = useState(defaultFlagValue) // ...that we can use as the default for our hook
useEffect(() => {
return client.onFeatureFlags(() => {
setFeatureEnabled(client.isFeatureEnabled(flag))
})
}, [client, flag])
return featureEnabled
}
It would be nice if this could be included in the official library 🙂
Edit: It would still be nice to have a workaround for nextjs
That custom hook suggestion can result in hydration errors, basically what they concluded themselves: https://github.com/PostHog/posthog-js/blob/26150b197dc1a2f5241b8d29ee7a359331a1a0a4/react/src/hooks/useFeatureFlagEnabled.ts#L8-L9
Not if you aren't using nextjs 🙂
Ah right, yeah it should be ok if it's only client-side we're talking about.
That custom hook suggestion can result in hydration errors, basically what they concluded themselves:
Though they could check whether or not feature flags were bootstraped (https://posthog.com/tutorials/nextjs-bootstrap-flags).
export function useBootstrapedFeatureFlagVariantKey(flag: string): string | boolean | undefined {
const client = usePostHog()
// Check if the feature flag is already bootstrapped. If so, use that value for (atleast) the first render
const bootstrapedFeatureFlag = client.config.bootstrap?.featureFlags ? client.getFeatureFlag(flag) : undefined;
const [featureFlagVariantKey, setFeatureFlagVariantKey] = useState<string | boolean | undefined>(bootstrapedFeatureFlag)
useEffect(() => {
return client.onFeatureFlags(() => {
setFeatureFlagVariantKey(client.getFeatureFlag(flag))
})
}, [client, flag])
return featureFlagVariantKey
}
Maybe interesting for another PR like #717, as you could also optionally enable it like this and guarentee backwards-compatibiltiy
export function useBootstrapedFeatureFlagVariantKey(flag: string, useBootstrap?: true): string | boolean | undefined
It seems that all react hooks initially render w/o the feature flag value and they will be populated after the internal useEffect has ran. See for example the following hook coming from
useFeatureFlagEnabled.ts
:Each and every time this hook is called, the initial (default)
featureEnabled
will beundefined
and after theuseEffect
has ran it will be set tofalse
ortrue
.According to the comment in the above function, someone has already thought about having the "default" value in the initial state but ran into hydration issues. This is true, but in theory it should be possible to write the flag state to a context and read values from there on subsequential renders.
Are there internal posthog limitations for why this wouldn't be possible?
Some background info: I'm running into a use-case with a next-app where a user lands on page
/foo
. That page will load feature flagflag-x
in order to show something. Then he navigates (client-side) to/bar
. That page also depends onflag-x
to show something. It is expected for the first page (/foo
) to shortly toggle betweenhide
andshow
of a piece of content because of PH loading the flags. But the second page should be able to render instantly with a hidden state, because it is rendered client-side and flags have already been loaded.