Skip to content

Commit c51db83

Browse files
authored
chore: adding better bootstrap support for react sdk (#1194)
This PR will allow developers to directly set bootstrap data on the sdk provider. I think this is helpful in order to reasonably preserve the non-deferred start case. <!-- devin-review-badge-begin --> --- <a href="https://app.devin.ai/review/launchdarkly/js-core/pull/1194" target="_blank"> <picture> <source media="(prefers-color-scheme: dark)" srcset="https://static.devin.ai/assets/gh-open-in-devin-review-dark.svg?v=1"> <img src="https://static.devin.ai/assets/gh-open-in-devin-review-light.svg?v=1" alt="Open with Devin"> </picture> </a> <!-- devin-review-badge-end --> <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Touches provider initialization behavior by altering the `client.start()` options, which could affect startup/flag hydration if callers relied on previous `startOptions` semantics. > > **Overview** > Adds a first-class `bootstrap` option to `createLDReactProvider`, and when auto-starting it now merges that value into the `client.start()` options (overriding any `startOptions.bootstrap`). > > Updates unit coverage to assert the new merge/precedence behavior, and extends the migration docs to explain how to pass bootstrap data in the new SDK. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit da8201d. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent d9a1bd7 commit c51db83

4 files changed

Lines changed: 71 additions & 3 deletions

File tree

packages/sdk/react/__tests__/client/provider/LDReactProvider.test.tsx

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ jest.mock('../../../src/client/LDReactClient', () => ({
2020
}));
2121

2222
// ─── createLDReactProviderWithClient ────────────────────────────────────────
23-
2423
it('renders children', () => {
2524
const client = makeMockClient();
2625
const Provider = createLDReactProviderWithClient(client);
@@ -219,6 +218,31 @@ describe('createLDReactProvider (convenience factory)', () => {
219218
expect(mockClient.start).toHaveBeenCalledWith(startOptions);
220219
});
221220

221+
it('merges bootstrap into startOptions when bootstrap is provided', () => {
222+
const bootstrapData = { $flagsState: { flag: { variation: 0 } }, $valid: true };
223+
createLDReactProvider('sdk-key', { kind: 'user', key: 'u1' }, { bootstrap: bootstrapData });
224+
225+
expect(mockClient.start).toHaveBeenCalledWith({ bootstrap: bootstrapData });
226+
});
227+
228+
it('merges bootstrap with existing startOptions', () => {
229+
const bootstrapData = { $flagsState: { flag: { variation: 0 } }, $valid: true };
230+
const startOptions = { timeout: 5 };
231+
createLDReactProvider(
232+
'sdk-key',
233+
{ kind: 'user', key: 'u1' },
234+
{ startOptions, bootstrap: bootstrapData },
235+
);
236+
237+
expect(mockClient.start).toHaveBeenCalledWith({ timeout: 5, bootstrap: bootstrapData });
238+
});
239+
240+
it('does not merge bootstrap into startOptions when bootstrap is not provided', () => {
241+
createLDReactProvider('sdk-key', { kind: 'user', key: 'u1' });
242+
243+
expect(mockClient.start).toHaveBeenCalledWith(undefined);
244+
});
245+
222246
it('uses provided reactContext option', () => {
223247
const CustomContext = initLDReactContext();
224248
const contextValues: LDReactClientContextValue[] = [];

packages/sdk/react/src/client/LDOptions.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,4 +70,17 @@ export interface LDReactProviderOptions {
7070
* @returns {LDReactClientContext} The react context for the LaunchDarkly client.
7171
*/
7272
reactContext?: LDReactClientContext;
73+
74+
/**
75+
* Bootstrap data from the server. Pass the result of `flagsState.toJSON()` obtained
76+
* from the server SDK's `allFlagsState` method.
77+
*
78+
* When provided, the client immediately uses these values before the first network
79+
* response arrives — eliminating the flag-fetch waterfall on page load.
80+
*
81+
* This is merged into `startOptions.bootstrap` when the client is started. If both
82+
* `bootstrap` and `startOptions.bootstrap` are provided, this top-level value takes
83+
* precedence.
84+
*/
85+
bootstrap?: unknown;
7386
}

packages/sdk/react/src/client/provider/LDReactProvider.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,12 +120,13 @@ export function createLDReactProvider(
120120
context: LDContext,
121121
options?: LDReactProviderOptions,
122122
): React.FC<{ children: React.ReactNode }> {
123-
const { deferInitialization, startOptions, reactContext, ldOptions } = options ?? {};
123+
const { deferInitialization, startOptions, reactContext, ldOptions, bootstrap } = options ?? {};
124124

125125
const client = createClient(clientSideID, context, ldOptions);
126126

127127
if (!deferInitialization) {
128-
client.start(startOptions);
128+
const effectiveStartOptions = bootstrap ? { ...startOptions, bootstrap } : startOptions;
129+
client.start(effectiveStartOptions);
129130
}
130131

131132
return createLDReactProviderWithClient(client, reactContext);

packages/sdk/react/temp_docs/MIGRATING.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,36 @@ state when initialization completes or when `client.identify()` is called.
179179

180180
---
181181

182+
## Bootstrap
183+
184+
### Old SDK
185+
186+
Bootstrap data was passed nested inside `options`:
187+
188+
```tsx
189+
// HOC
190+
withLDProvider({ clientSideID: 'your-id', options: { bootstrap: myData } })(App);
191+
192+
// Async HOC
193+
const LDProvider = await asyncWithLDProvider({ clientSideID: 'your-id', options: { bootstrap: myData } });
194+
```
195+
196+
### New SDK
197+
198+
Bootstrap is a first-class option on `createLDReactProvider`:
199+
200+
```tsx
201+
import { createLDReactProvider } from '@launchdarkly/react-sdk';
202+
203+
const LDProvider = createLDReactProvider('your-client-side-id', { kind: 'user', key: 'user-key' }, {
204+
bootstrap: myData,
205+
});
206+
```
207+
208+
The `bootstrap` data format is unchanged from the old SDK. You can pass either a plain key-value
209+
object (`{ 'my-flag': true }`) or the output of `allFlagsState().toJSON()`, which includes
210+
`$flagsState` and `$valid` metadata.
211+
182212
## Removed APIs
183213

184214
| Old API | Status | Replacement |

0 commit comments

Comments
 (0)