Skip to content

Commit 80fb766

Browse files
committed
Support React StrictMode (avoid double render)
1 parent a1e5015 commit 80fb766

File tree

2 files changed

+57
-17
lines changed

2 files changed

+57
-17
lines changed

src/react/index.tsx

Lines changed: 56 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { UnleashClient, type IConfig } from 'unleash-proxy-client';
22
import { FlagProvider as DefaultFlagProvider } from '@unleash/proxy-client-react';
3-
import { initUnleashToolbar } from '../index';
3+
import { wrapUnleashClient, ToolbarStateManager, UnleashToolbar } from '../index';
44
import type { InitToolbarOptions } from '../types';
5-
import React, { useRef } from 'react';
5+
import React, { useRef, useEffect } from 'react';
66

77
/**
88
* Base props shared by both config and client variants
@@ -121,27 +121,66 @@ export function UnleashToolbarProvider({
121121
);
122122
}
123123

124-
// Wrap client immediately on first render (not in useEffect)
125-
// Use a lazy initializer to ensure it only runs once
124+
const toolbarRef = useRef<any>(null);
125+
const stateManagerRef = useRef<any>(null);
126+
127+
// Wrap client synchronously (idempotent, captures flag evaluations)
128+
// This ensures the wrapper intercepts all flag evaluations from the first render
126129
const wrappedClientRef = useRef<UnleashClient | null>(null);
127130

128131
if (wrappedClientRef.current === null) {
129-
// Only initialize on client side (not during SSR)
130-
if (typeof window !== 'undefined') {
131-
// Create client from config if provided
132-
const unleashClient = client || new UnleashClient(config!);
133-
134-
// Skip toolbar initialization if toolbarOptions is explicitly undefined (production mode)
135-
if (toolbarOptions === undefined) {
136-
wrappedClientRef.current = unleashClient;
137-
} else {
138-
wrappedClientRef.current = initUnleashToolbar(unleashClient, toolbarOptions);
132+
const unleashClient = client || new UnleashClient(config!);
133+
134+
if (toolbarOptions !== undefined) {
135+
// Create state manager that will be shared between wrapper and toolbar
136+
const storageMode = toolbarOptions.storageMode || 'local';
137+
const storageKey = toolbarOptions.storageKey || 'unleash-toolbar-state';
138+
const sortAlphabetically = toolbarOptions.sortAlphabetically || false;
139+
const stateManager = new ToolbarStateManager(storageMode, storageKey, sortAlphabetically);
140+
stateManagerRef.current = stateManager;
141+
142+
if (toolbarOptions.enableCookieSync) {
143+
stateManager.enableCookieSync();
139144
}
145+
146+
// Wrap client synchronously with shared state manager
147+
wrappedClientRef.current = wrapUnleashClient(unleashClient, stateManager);
140148
} else {
141-
// SSR: create unwrapped client
142-
wrappedClientRef.current = client || new UnleashClient(config!);
149+
// no toolbar - just use original client
150+
wrappedClientRef.current = unleashClient;
143151
}
144152
}
153+
154+
// Create toolbar UI in useEffect (StrictMode-safe)
155+
useEffect(() => {
156+
// Only create toolbar if:
157+
// 1. toolbarOptions is defined
158+
// 2. No toolbar ref yet
159+
// 3. State manager exists
160+
// 4. Wrapped client doesn't already have a toolbar (prevents StrictMode duplicates)
161+
if (toolbarOptions !== undefined &&
162+
!toolbarRef.current &&
163+
stateManagerRef.current &&
164+
!(wrappedClientRef.current as any)?.__toolbar) {
165+
// Create toolbar with the same state manager used for wrapping
166+
const toolbar = new UnleashToolbar(stateManagerRef.current, wrappedClientRef.current as any, toolbarOptions);
167+
(wrappedClientRef.current as any).__toolbar = toolbar;
168+
toolbarRef.current = toolbar;
169+
170+
// Expose globally
171+
if (typeof window !== 'undefined') {
172+
(window as any).unleashToolbar = toolbar;
173+
}
174+
}
175+
176+
// Cleanup toolbar on unmount
177+
return () => {
178+
if (toolbarRef.current && typeof toolbarRef.current.destroy === 'function') {
179+
toolbarRef.current.destroy();
180+
toolbarRef.current = null;
181+
}
182+
};
183+
}, []);
145184

146185
return (
147186
<FlagProvider
@@ -152,4 +191,4 @@ export function UnleashToolbarProvider({
152191
{children}
153192
</FlagProvider>
154193
);
155-
}
194+
}

src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,4 +181,5 @@ export interface UnleashToolbarInstance {
181181
*/
182182
export interface WrappedUnleashClient extends UnleashClient {
183183
__original: UnleashClient;
184+
__toolbar?: UnleashToolbarInstance;
184185
}

0 commit comments

Comments
 (0)