Skip to content

Commit 405d61d

Browse files
authored
fix: memoize React client to provide stability (#1276)
## This PR 👋 Maybe I'm missing something, but `useOpenFeatureClient` currently returns an unstable `client` that is recreated every time the provider re-renders, if you pass a domain into the provider instead of a specific client. This PR wraps the creation of that client when a domain is passed in `useMemo`, which should ensure that it is then stable for downstream usage via the `useOpenFeatureClient` hook. As an example, due to `client` being unstable (combined with the `shouldRunNow` behaviour in the client), this logs `ready` every time the provider re-renders rather than just once when the client is actually ready. ```ts const client = useOpenFeatureClient(); useEffect(() => { const ready = () => { console.log('ready'); }; client.addHandler(ProviderEvents.Ready, ready); return () => { client.removeHandler(ProviderEvents.Ready, ready); }; }, [client]); ``` The closest workaround I've found currently is checking if the domain matches w/ `useState` + `useEffect`: ```ts const client = useOpenFeatureClient(); const [stableClient, setStableClient] = useState(client); useEffect(() => { setStableClient((existing) => (existing.metadata.domain === client.metadata.domain ? existing : client)); }, [client]); useEffect(() => { const ready = () => { console.log('ready'); }; stableClient.addHandler(ProviderEvents.Ready, ready); return () => { stableClient.removeHandler(ProviderEvents.Ready, ready); }; }, [stableClient]); --------- Signed-off-by: Matt Cowley <[email protected]> Signed-off-by: MattIPv4 <[email protected]>
1 parent 27666b8 commit 405d61d

File tree

2 files changed

+16
-4
lines changed

2 files changed

+16
-4
lines changed

packages/react/src/provider/provider.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,7 @@ type ProviderProps = {
3232
* @returns {OpenFeatureProvider} context provider
3333
*/
3434
export function OpenFeatureProvider({ client, domain, children, ...options }: ProviderProps): JSX.Element {
35-
if (!client) {
36-
client = OpenFeature.getClient(domain);
37-
}
35+
const stableClient = React.useMemo(() => client || OpenFeature.getClient(domain), [client, domain]);
3836

39-
return <Context.Provider value={{ client, options, domain }}>{children}</Context.Provider>;
37+
return <Context.Provider value={{ client: stableClient, options, domain }}>{children}</Context.Provider>;
4038
}

packages/react/test/provider.spec.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,20 @@ describe('OpenFeatureProvider', () => {
7878

7979
expect(result.current.metadata.domain).toEqual(DOMAIN);
8080
});
81+
82+
it('should return a stable client across renders', () => {
83+
const wrapper = ({ children }: Parameters<typeof OpenFeatureProvider>[0]) => (
84+
<OpenFeatureProvider domain={DOMAIN}>{children}</OpenFeatureProvider>
85+
);
86+
87+
const { result, rerender } = renderHook(() => useOpenFeatureClient(), { wrapper });
88+
89+
const firstClient = result.current;
90+
rerender();
91+
const secondClient = result.current;
92+
93+
expect(firstClient).toBe(secondClient);
94+
});
8195
});
8296
});
8397

0 commit comments

Comments
 (0)