Skip to content

Commit 6051693

Browse files
authored
Merge branch 'main' into dependabot/npm_and_yarn/rollup/plugin-typescript-12.1.1
2 parents 32da798 + 186c66f commit 6051693

19 files changed

+271
-69
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
**/junit.xml
44
npm-debug.log
55
yarn-error.log
6+
pnpm-debug.log
67
node_modules
78
dist
89
lib
@@ -12,5 +13,6 @@ test-types.js
1213
docs/build/
1314
yarn.lock
1415
package-lock.json
16+
pnpm-lock.yaml
1517
.npmrc
1618
docs/

.release-please-manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
".": "3.4.0"
2+
".": "3.5.0"
33
}

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@
22

33
All notable changes to the LaunchDarkly Client-side SDK for React will be documented in this file. For the source code for versions 2.13.0 and earlier, see the corresponding tags in the [js-client-sdk](https://github.com/launchdarkly/js-client-sdk) repository; this code was previously in a monorepo package there. See also the [JavaScript SDK changelog](https://github.com/launchdarkly/js-client-sdk/blob/main/CHANGELOG.md), since the React SDK inherits all of the underlying functionality of the JavaScript SDK; this file covers only changes that are specific to the React interface. This project adheres to [Semantic Versioning](http://semver.org).
44

5+
## [3.5.0](https://github.com/launchdarkly/react-client-sdk/compare/launchdarkly-react-client-sdk-v3.4.0...launchdarkly-react-client-sdk-v3.5.0) (2024-10-18)
6+
7+
8+
### Features
9+
10+
* Add support for client-side prerequisite events. ([3cb6060](https://github.com/launchdarkly/react-client-sdk/commit/3cb606033d418a08144f88e0f632433951216c9c))
11+
* Add support for synchronous inspectors. ([3cb6060](https://github.com/launchdarkly/react-client-sdk/commit/3cb606033d418a08144f88e0f632433951216c9c))
12+
513
## [3.4.0](https://github.com/launchdarkly/react-client-sdk/compare/launchdarkly-react-client-sdk-v3.3.2...launchdarkly-react-client-sdk-v3.4.0) (2024-08-02)
614

715

examples/deferred-initialization/src/App.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import logo from './logo.svg';
33
import Welcome from './welcome';
44
import { LDProvider, LDContext } from 'launchdarkly-react-client-sdk';
55

6-
// tslint:disable-next-line:no-import-side-effect
76
import './App.css';
87

98
const clientSideID = process.env.REACT_APP_LD_CLIENT_SIDE_ID ?? '';

examples/deferred-initialization/src/index.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
import React from 'react';
2-
// tslint:disable-next-line:no-submodule-imports
32
import ReactDOM from 'react-dom/client';
43
import App from './App';
54
import reportWebVitals from './reportWebVitals';
65

7-
// tslint:disable-next-line:no-import-side-effect
86
import './index.css';
97

108
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);

examples/deferred-initialization/src/welcome.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import React from 'react';
22
import { useFlags } from 'launchdarkly-react-client-sdk';
33

4-
// tslint:disable-next-line:no-import-side-effect
54
import './App.css';
65

76
function Welcome() {

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "launchdarkly-react-client-sdk",
3-
"version": "3.4.0",
3+
"version": "3.5.0",
44
"description": "LaunchDarkly SDK for React",
55
"author": "LaunchDarkly <[email protected]>",
66
"license": "Apache-2.0",
@@ -78,14 +78,14 @@
7878
"rollup-plugin-dts": "^6.1.0",
7979
"rollup-plugin-esbuild": "^6.1.1",
8080
"ts-jest": "^29.2.2",
81-
"tslint": "^6.1.3",
81+
"tslib": "^2.8.1",
8282
"typedoc": "^0.26.5",
8383
"typescript": "^5.5.4",
8484
"typescript-eslint": "^8.0.0"
8585
},
8686
"dependencies": {
8787
"hoist-non-react-statics": "^3.3.2",
88-
"launchdarkly-js-client-sdk": "^3.3.0",
88+
"launchdarkly-js-client-sdk": "^3.5.0",
8989
"lodash.camelcase": "^4.3.0"
9090
},
9191
"peerDependencies": {

src/asyncWithLDProvider.test.tsx

Lines changed: 118 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,13 @@ import React from 'react';
22
import '@testing-library/dom';
33
import '@testing-library/jest-dom';
44
import { render } from '@testing-library/react';
5-
import { initialize, LDContext, LDFlagChangeset, LDOptions } from 'launchdarkly-js-client-sdk';
5+
import { initialize, LDClient, LDContext, LDFlagChangeset, LDOptions } from 'launchdarkly-js-client-sdk';
66
import { AsyncProviderConfig, LDReactOptions } from './types';
7-
import { Consumer } from './context';
7+
import { Consumer, reactSdkContextFactory } from './context';
88
import asyncWithLDProvider from './asyncWithLDProvider';
99
import wrapperOptions from './wrapperOptions';
1010
import { fetchFlags } from './utils';
1111

12-
1312
jest.mock('launchdarkly-js-client-sdk', () => {
1413
const actual = jest.requireActual('launchdarkly-js-client-sdk');
1514

@@ -351,4 +350,120 @@ describe('asyncWithLDProvider', () => {
351350

352351
expect(receivedNode).toHaveTextContent('{"testFlag":false}');
353352
});
353+
354+
test('custom context is provided to consumer', async () => {
355+
const CustomContext = reactSdkContextFactory();
356+
const customLDClient = {
357+
on: jest.fn((_: string, cb: () => void) => {
358+
cb();
359+
}),
360+
off: jest.fn(),
361+
allFlags: jest.fn().mockReturnValue({ 'context-test-flag': true }),
362+
variation: jest.fn((_: string, v) => v),
363+
waitForInitialization: jest.fn(),
364+
};
365+
const config: AsyncProviderConfig = {
366+
clientSideID,
367+
ldClient: customLDClient as unknown as LDClient,
368+
reactOptions: {
369+
reactContext: CustomContext,
370+
},
371+
};
372+
const originalUtilsModule = jest.requireActual('./utils');
373+
mockFetchFlags.mockImplementation(originalUtilsModule.fetchFlags);
374+
375+
const LDProvider = await asyncWithLDProvider(config);
376+
const LaunchDarklyApp = (
377+
<LDProvider>
378+
<CustomContext.Consumer>
379+
{({ flags }) => {
380+
return (
381+
<span>
382+
flag is {flags.contextTestFlag === undefined ? 'undefined' : JSON.stringify(flags.contextTestFlag)}
383+
</span>
384+
);
385+
}}
386+
</CustomContext.Consumer>
387+
</LDProvider>
388+
);
389+
390+
const { findByText } = render(LaunchDarklyApp);
391+
expect(await findByText('flag is true')).not.toBeNull();
392+
393+
const receivedNode = await renderWithConfig({ clientSideID });
394+
expect(receivedNode).not.toHaveTextContent('{"contextTestFlag":true}');
395+
});
396+
397+
test('multiple providers', async () => {
398+
const customLDClient1 = {
399+
on: jest.fn((_: string, cb: () => void) => {
400+
cb();
401+
}),
402+
off: jest.fn(),
403+
allFlags: jest.fn().mockReturnValue({ 'context1-test-flag': true }),
404+
variation: jest.fn((_: string, v) => v),
405+
waitForInitialization: jest.fn(),
406+
};
407+
const customLDClient2 = {
408+
on: jest.fn((_: string, cb: () => void) => {
409+
cb();
410+
}),
411+
off: jest.fn(),
412+
allFlags: jest.fn().mockReturnValue({ 'context2-test-flag': true }),
413+
variation: jest.fn((_: string, v) => v),
414+
waitForInitialization: jest.fn(),
415+
};
416+
const originalUtilsModule = jest.requireActual('./utils');
417+
mockFetchFlags.mockImplementation(originalUtilsModule.fetchFlags);
418+
419+
const CustomContext1 = reactSdkContextFactory();
420+
const LDProvider1 = await asyncWithLDProvider({
421+
clientSideID,
422+
ldClient: customLDClient1 as unknown as LDClient,
423+
reactOptions: {
424+
reactContext: CustomContext1,
425+
},
426+
});
427+
const CustomContext2 = reactSdkContextFactory();
428+
const LDProvider2 = await asyncWithLDProvider({
429+
clientSideID,
430+
ldClient: customLDClient2 as unknown as LDClient,
431+
reactOptions: {
432+
reactContext: CustomContext2,
433+
},
434+
});
435+
const safeValue = (val?: boolean) => (val === undefined ? 'undefined' : JSON.stringify(val));
436+
const LaunchDarklyApp = (
437+
<LDProvider1>
438+
<LDProvider2>
439+
<CustomContext1.Consumer>
440+
{({ flags }) => {
441+
return (
442+
<>
443+
<span>consumer 1, flag 1 is {safeValue(flags.context1TestFlag)}</span>
444+
<span>consumer 1, flag 2 is {safeValue(flags.context2TestFlag)}</span>
445+
</>
446+
);
447+
}}
448+
</CustomContext1.Consumer>
449+
<CustomContext2.Consumer>
450+
{({ flags }) => {
451+
return (
452+
<>
453+
<span>consumer 2, flag 1 is {safeValue(flags.context1TestFlag)}</span>
454+
<span>consumer 2, flag 2 is {safeValue(flags.context2TestFlag)}</span>
455+
</>
456+
);
457+
}}
458+
</CustomContext2.Consumer>
459+
</LDProvider2>
460+
</LDProvider1>
461+
);
462+
463+
const { findByText } = render(LaunchDarklyApp);
464+
expect(await findByText('consumer 1, flag 1 is true')).not.toBeNull();
465+
expect(await findByText('consumer 1, flag 2 is undefined')).not.toBeNull();
466+
expect(await findByText('consumer 2, flag 1 is undefined')).not.toBeNull();
467+
expect(await findByText('consumer 2, flag 2 is true')).not.toBeNull();
468+
});
354469
});

src/asyncWithLDProvider.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import React, { useState, useEffect, ReactNode } from 'react';
22
import { initialize, LDFlagChangeset } from 'launchdarkly-js-client-sdk';
33
import { AsyncProviderConfig, defaultReactOptions } from './types';
4-
import { Provider } from './context';
54
import { fetchFlags, getContextOrUser, getFlattenedFlagsFromChangeset } from './utils';
65
import getFlagsProxy from './getFlagsProxy';
76
import wrapperOptions from './wrapperOptions';
@@ -104,7 +103,9 @@ export default async function asyncWithLDProvider(config: AsyncProviderConfig) {
104103
// unproxiedFlags is for internal use only. Exclude it from context.
105104
const { unproxiedFlags: _, ...rest } = ldData;
106105

107-
return <Provider value={rest}>{children}</Provider>;
106+
const { reactContext } = reactOptions;
107+
108+
return <reactContext.Provider value={rest}>{children}</reactContext.Provider>;
108109
};
109110

110111
return LDProvider;

src/context.ts

Lines changed: 8 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,17 @@
11
import { createContext } from 'react';
2-
import { LDClient, LDFlagSet } from 'launchdarkly-js-client-sdk';
3-
import { LDFlagKeyMap } from './types';
2+
import { ReactSdkContext } from './types';
43

54
/**
6-
* The sdk context stored in the Provider state and passed to consumers.
5+
* `reactSdkContextFactory` is a function useful for creating a React context for use with
6+
* all the providers and consumers in this library.
7+
*
8+
* @return a React Context
79
*/
8-
interface ReactSdkContext {
9-
/**
10-
* JavaScript proxy that will trigger a LDClient#variation call on flag read in order
11-
* to register a flag evaluation event in LaunchDarkly. Empty {} initially
12-
* until flags are fetched from the LaunchDarkly servers.
13-
*/
14-
flags: LDFlagSet;
15-
16-
/**
17-
* Map of camelized flag keys to their original unmodified form. Empty if useCamelCaseFlagKeys option is false.
18-
*/
19-
flagKeyMap: LDFlagKeyMap;
20-
21-
/**
22-
* An instance of `LDClient` from the LaunchDarkly JS SDK (`launchdarkly-js-client-sdk`).
23-
* This will be be undefined initially until initialization is complete.
24-
*
25-
* @see https://docs.launchdarkly.com/sdk/client-side/javascript
26-
*/
27-
ldClient?: LDClient;
28-
29-
/**
30-
* LaunchDarkly client initialization error, if there was one.
31-
*/
32-
error?: Error;
33-
}
34-
10+
const reactSdkContextFactory = () => createContext<ReactSdkContext>({ flags: {}, flagKeyMap: {}, ldClient: undefined });
3511
/**
3612
* @ignore
3713
*/
38-
const context = createContext<ReactSdkContext>({ flags: {}, flagKeyMap: {}, ldClient: undefined });
14+
const context = reactSdkContextFactory();
3915
const {
4016
/**
4117
* @ignore
@@ -47,5 +23,5 @@ const {
4723
Consumer,
4824
} = context;
4925

50-
export { Provider, Consumer, ReactSdkContext };
26+
export { Provider, Consumer, ReactSdkContext, reactSdkContextFactory };
5127
export default context;

0 commit comments

Comments
 (0)