Skip to content

Commit 176a6a1

Browse files
authored
chore: Add React SDK FDv2 data saving mode example (#1425)
## Summary Adds `packages/sdk/react/examples/example-fdv2`, a minimal Vite + React app for exercising the React SDK's FDv2 **data saving mode** (`dataSystem`) configuration -- the React counterpart to the existing browser and React Native `example-fdv2` apps. Intended for manual testing of the EAP feature. What it does: - Enables the FDv2 data system via the provider's `ldOptions` (`dataSystem: {}`) in `src/LDClient.tsx`, with a commented manual-mode-switching variant. - Evaluates a flag with `useBoolVariation` (updates live). - Drives `setConnectionMode` (streaming / polling / offline / one-shot / background / automatic), `setStreaming`, and `identify` (context switching) through `useLDClient()`. - Includes an action log. Also registers the new workspace in the root `package.json`. ### Run it ```bash yarn workspaces foreach -pR --topological-dev --from '@launchdarkly/react-sdk' run build LAUNCHDARKLY_CLIENT_SIDE_ID=your-id yarn workspace @launchdarkly/react-sdk-example-fdv2 start ``` ### Validation - `vite build` succeeds (30 modules bundle cleanly). - The `dataSystem` config typechecks against the React SDK. - `tsc` reports one pre-existing `@types/react` skew error (`bigint` not assignable to `ReactNode`) that also affects the existing `hello-react` example; it does not affect the Vite dev/build runtime. ### Notes - Stacked on the FDv2 EAP branch (`sdk-2460/fdv2-eap-browser-react-native`, PR #1419) because the example relies on the public `dataSystem` option that the EAP work exposes. Rebase onto `main` once #1419 merges. - The example pins `@launchdarkly/react-sdk` to the workspace version (resolves to the local build), consistent with the other example-fdv2 apps. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes affect CI contract-test harness versions and RN SDK config translation for FDv2; runtime SDK behavior is mostly doc/API surface, but mis-mapped `dataSystem` in contract tests could hide regressions. > > **Overview** > Adds a new Vite + React **data saving mode** example at `packages/sdk/react/examples/features/data-saving-mode`, registers it in the root workspace and `release-please-config.json`, and documents manual EAP testing (`dataSystem`, `setConnectionMode`, `setStreaming`, `identify`). > > **FDv2 EAP documentation:** Replaces `@internal` / experimental warnings on `dataSystem` and FDv2 `setConnectionMode` / `getConnectionMode` in shared `LDOptions`, browser, and React Native with **Early Access** messaging and a link to the data-saving-mode docs; FDv2 examples drop `@ts-ignore` where types are now public. > > **Contract test CI:** Browser workflow runs **FDv1** then **FDv2** harness (`version: v3`, separate suppressions) without stopping the service between runs. React Native CI drops the pinned harness tarball, passes `GITHUB_TOKEN`, and runs FDv1 + FDv2 via the official harness downloader; `ClientEntity` maps harness `dataSystem` (connection modes, initializers/synchronizers, payload filter) into RN `LDOptions`, with FDv1 streaming/polling config unchanged when `dataSystem` is absent. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit ff9253f. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 6ee9c51 commit 176a6a1

15 files changed

Lines changed: 373 additions & 0 deletions

File tree

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"packages/sdk/fastly/example",
2323
"packages/sdk/react",
2424
"packages/sdk/react/contract-tests",
25+
"packages/sdk/react/examples/features/data-saving-mode",
2526
"packages/sdk/react/examples/features/bootstrap",
2627
"packages/sdk/react/examples/hello-react",
2728
"packages/sdk/react/examples/react-server-example",
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Set when running yarn start / yarn build
2+
# (e.g. LAUNCHDARKLY_CLIENT_SIDE_ID=xxx LAUNCHDARKLY_FLAG_KEY=my-flag yarn start).
3+
4+
LAUNCHDARKLY_CLIENT_SIDE_ID=
5+
LAUNCHDARKLY_FLAG_KEY=sample-feature
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
6+
# production
7+
/dist
8+
9+
# misc
10+
.DS_Store
11+
.env.local
12+
.env.development.local
13+
.env.test.local
14+
.env.production.local
15+
.env
16+
17+
npm-debug.log*
18+
yarn-debug.log*
19+
yarn-error.log*
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# LaunchDarkly React SDK FDv2 example
2+
3+
A minimal [Vite](https://vitejs.dev/) + React app that exercises the React SDK's
4+
FDv2 **data saving mode** (`dataSystem`) configuration.
5+
6+
> [!NOTE]
7+
> Data saving mode is a LaunchDarkly Early Access Program (EAP) feature. The
8+
> `dataSystem` option and its behaviors are not stable and may change before
9+
> General Availability.
10+
11+
## What it demonstrates
12+
13+
- Enabling the FDv2 data system through the provider's `ldOptions`
14+
(`dataSystem: {}`), in [`src/LDClient.tsx`](./src/LDClient.tsx).
15+
- Evaluating a flag with `useBoolVariation`, which updates live as the data
16+
system delivers changes.
17+
- Switching the connection mode at runtime (`streaming`, `polling`, `offline`,
18+
`one-shot`, `background`, or automatic) via `useLDClient().setConnectionMode`.
19+
- Toggling streaming via `setStreaming`.
20+
- Switching the evaluation context via `identify`.
21+
22+
## Run it
23+
24+
This example uses the workspace build of `@launchdarkly/react-sdk`. From the
25+
repository root, build the SDK and its dependencies first:
26+
27+
```bash
28+
yarn workspaces foreach -pR --topological-dev --from '@launchdarkly/react-sdk' run build
29+
```
30+
31+
Then start the example with your client-side ID (and optionally a flag key):
32+
33+
```bash
34+
LAUNCHDARKLY_CLIENT_SIDE_ID=your-client-side-id \
35+
LAUNCHDARKLY_FLAG_KEY=your-flag-key \
36+
yarn workspace @launchdarkly/react-sdk-example-data-saving-mode start
37+
```
38+
39+
Open the URL Vite prints (default <http://localhost:5173>).
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>LaunchDarkly React SDK FDv2 Example</title>
7+
</head>
8+
<body>
9+
<div id="root"></div>
10+
<script type="module" src="/src/index.tsx"></script>
11+
</body>
12+
</html>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"name": "@launchdarkly/react-sdk-example-data-saving-mode",
3+
"version": "0.1.0",
4+
"private": true,
5+
"dependencies": {
6+
"@launchdarkly/react-sdk": "4.1.0",
7+
"react": "^18.2.0",
8+
"react-dom": "^18.2.0"
9+
},
10+
"scripts": {
11+
"start": "vite",
12+
"build": "vite build",
13+
"preview": "vite preview",
14+
"tsc": "tsc"
15+
},
16+
"devDependencies": {
17+
"@types/node": "^16.18.18",
18+
"@types/react": "^18.0.28",
19+
"@types/react-dom": "^18.0.11",
20+
"@vitejs/plugin-react": "^4",
21+
"typescript": "^5.0.0",
22+
"vite": "^6"
23+
}
24+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
.App {
2+
max-width: 640px;
3+
margin: 0 auto;
4+
padding: 24px 16px 48px;
5+
}
6+
7+
.App h1 {
8+
font-size: 22px;
9+
}
10+
11+
.App h2 {
12+
font-size: 15px;
13+
text-transform: uppercase;
14+
letter-spacing: 0.04em;
15+
color: #5a5f6b;
16+
margin-bottom: 6px;
17+
}
18+
19+
.status {
20+
font-style: italic;
21+
color: #5a5f6b;
22+
}
23+
24+
section {
25+
border-top: 1px solid #e1e3e8;
26+
padding: 12px 0;
27+
}
28+
29+
.buttons {
30+
display: flex;
31+
flex-wrap: wrap;
32+
gap: 8px;
33+
}
34+
35+
.buttons button {
36+
background-color: #405bff;
37+
color: #fff;
38+
border: none;
39+
border-radius: 6px;
40+
padding: 8px 12px;
41+
font-size: 14px;
42+
cursor: pointer;
43+
}
44+
45+
.buttons button:hover {
46+
background-color: #2f47cc;
47+
}
48+
49+
.log {
50+
background-color: #1a1a1a;
51+
color: #ffa500;
52+
font-family: monospace;
53+
font-size: 12px;
54+
border-radius: 6px;
55+
padding: 10px;
56+
max-height: 200px;
57+
overflow: auto;
58+
white-space: pre-wrap;
59+
}
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import { useState } from 'react';
2+
3+
import {
4+
useBoolVariation,
5+
useInitializationStatus,
6+
useLDClient,
7+
} from '@launchdarkly/react-sdk';
8+
import type { FDv2ConnectionMode, LDContext } from '@launchdarkly/react-sdk';
9+
10+
import './App.css';
11+
12+
// Set FLAG_KEY to the feature flag key you want to evaluate.
13+
const FLAG_KEY = import.meta.env.LAUNCHDARKLY_FLAG_KEY ?? 'sample-feature';
14+
15+
// FDv2 connection modes the data system can use.
16+
const CONNECTION_MODES: FDv2ConnectionMode[] = [
17+
'streaming',
18+
'polling',
19+
'offline',
20+
'one-shot',
21+
'background',
22+
];
23+
24+
const CONTEXTS: LDContext[] = [
25+
{ kind: 'user', key: 'example-user-key', name: 'Sandy' },
26+
{ kind: 'user', key: 'example-user-key-2', name: 'Alex' },
27+
];
28+
29+
function App() {
30+
const { status } = useInitializationStatus();
31+
const flagValue = useBoolVariation(FLAG_KEY, false);
32+
const ldc = useLDClient();
33+
34+
const [mode, setMode] = useState<string>('automatic');
35+
const [streaming, setStreaming] = useState<string>('default');
36+
const [contextIndex, setContextIndex] = useState(0);
37+
const [log, setLog] = useState<string[]>([]);
38+
39+
const addLog = (line: string) =>
40+
setLog((prev) => [`${new Date().toISOString().slice(11, 23)} ${line}`, ...prev].slice(0, 25));
41+
42+
const onSetConnectionMode = (next?: FDv2ConnectionMode) => {
43+
ldc.setConnectionMode(next);
44+
setMode(next ?? 'automatic');
45+
addLog(`setConnectionMode(${next ?? 'undefined'})`);
46+
};
47+
48+
const onSetStreaming = (next?: boolean) => {
49+
ldc.setStreaming(next);
50+
setStreaming(next === undefined ? 'default' : String(next));
51+
addLog(`setStreaming(${next === undefined ? 'undefined' : next})`);
52+
};
53+
54+
const onSwitchContext = async () => {
55+
const next = (contextIndex + 1) % CONTEXTS.length;
56+
setContextIndex(next);
57+
addLog(`identify(${JSON.stringify(CONTEXTS[next])})`);
58+
const result = await ldc.identify(CONTEXTS[next]);
59+
addLog(`identify result: ${result.status}`);
60+
};
61+
62+
const ready = status !== 'initializing';
63+
let statusMessage = 'Initializing…';
64+
if (status === 'complete') {
65+
statusMessage = 'SDK successfully initialized.';
66+
} else if (status === 'failed') {
67+
statusMessage = 'SDK failed to initialize. Check your client-side ID and network.';
68+
}
69+
70+
return (
71+
<div className="App">
72+
<h1>LaunchDarkly React FDv2 Demo</h1>
73+
<p className="status">{statusMessage}</p>
74+
75+
<section>
76+
<h2>Flag</h2>
77+
<p>
78+
<code>{FLAG_KEY}</code> = <strong>{ready ? String(flagValue) : '…'}</strong>
79+
</p>
80+
</section>
81+
82+
<section>
83+
<h2>Connection mode</h2>
84+
<p>
85+
Current: <strong>{mode}</strong>
86+
</p>
87+
<div className="buttons">
88+
{CONNECTION_MODES.map((m) => (
89+
<button key={m} onClick={() => onSetConnectionMode(m)}>
90+
{m}
91+
</button>
92+
))}
93+
<button onClick={() => onSetConnectionMode(undefined)}>automatic (clear)</button>
94+
</div>
95+
</section>
96+
97+
<section>
98+
<h2>Streaming</h2>
99+
<p>
100+
Current: <strong>{streaming}</strong>
101+
</p>
102+
<div className="buttons">
103+
<button onClick={() => onSetStreaming(true)}>setStreaming(true)</button>
104+
<button onClick={() => onSetStreaming(false)}>setStreaming(false)</button>
105+
<button onClick={() => onSetStreaming(undefined)}>setStreaming(undefined)</button>
106+
</div>
107+
</section>
108+
109+
<section>
110+
<h2>Context</h2>
111+
<p>
112+
Current: <code>{JSON.stringify(CONTEXTS[contextIndex])}</code>
113+
</p>
114+
<div className="buttons">
115+
<button onClick={onSwitchContext}>Switch context (identify)</button>
116+
</div>
117+
</section>
118+
119+
<section>
120+
<h2>Log</h2>
121+
<pre className="log">{log.join('\n')}</pre>
122+
</section>
123+
</div>
124+
);
125+
}
126+
127+
export default App;
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { createLDReactProvider, LDContext, LDReactProviderOptions } from '@launchdarkly/react-sdk';
2+
3+
const LAUNCHDARKLY_CLIENT_SIDE_ID = import.meta.env.LAUNCHDARKLY_CLIENT_SIDE_ID ?? '';
4+
5+
// The initial evaluation context. This context should appear on your
6+
// LaunchDarkly contexts dashboard soon after you run the demo.
7+
export const initialContext: LDContext = {
8+
kind: 'user',
9+
key: 'example-user-key',
10+
name: 'Sandy',
11+
};
12+
13+
const options: LDReactProviderOptions = {
14+
ldOptions: {
15+
// Enable the FDv2 data system. An empty object opts in with default
16+
// behavior: a streaming connection for real-time flag updates, with
17+
// polling as a fallback.
18+
//
19+
// Data saving mode is an Early Access feature. To pin a specific
20+
// connection mode instead, use manual mode switching, for example:
21+
// dataSystem: {
22+
// automaticModeSwitching: { type: 'manual', initialConnectionMode: 'polling' },
23+
// },
24+
dataSystem: {},
25+
},
26+
};
27+
28+
export const LDReactProvider = createLDReactProvider(
29+
LAUNCHDARKLY_CLIENT_SIDE_ID,
30+
initialContext,
31+
options,
32+
);
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
body {
2+
margin: 0;
3+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
4+
background-color: #f4f5f8;
5+
color: #21242b;
6+
}

0 commit comments

Comments
 (0)