diff --git a/src/asyncWithLDProvider.test.tsx b/src/asyncWithLDProvider.test.tsx
index 2d08746..f9796d2 100644
--- a/src/asyncWithLDProvider.test.tsx
+++ b/src/asyncWithLDProvider.test.tsx
@@ -179,4 +179,19 @@ describe('asyncWithLDProvider', () => {
expect(mockInitLDClient).toHaveBeenCalledWith(clientSideID, user, defaultReactOptions, options, flags);
expect(receivedNode).toHaveTextContent('{"devTestFlag":true,"launchDoggly":true}');
});
+
+ test('only updates to subscribed flags are pushed to the Provider', async () => {
+ mockInitLDClient.mockImplementation(() => ({
+ flags: { testFlag: 2 },
+ ldClient: mockLDClient,
+ }));
+ mockLDClient.on.mockImplementation((e: string, cb: (c: LDFlagChangeset) => void) => {
+ cb({ 'test-flag': { current: 3, previous: 2 }, 'another-test-flag': { current: false, previous: true } });
+ });
+ const options: LDOptions = {};
+ const subscribedFlags = { 'test-flag': 1 };
+ const receivedNode = await renderWithConfig({ clientSideID, user, options, flags: subscribedFlags });
+
+ expect(receivedNode).toHaveTextContent('{"testFlag":3}');
+ });
});
diff --git a/src/asyncWithLDProvider.tsx b/src/asyncWithLDProvider.tsx
index 8b35a61..688d56e 100644
--- a/src/asyncWithLDProvider.tsx
+++ b/src/asyncWithLDProvider.tsx
@@ -1,10 +1,9 @@
import React, { useState, useEffect, FunctionComponent } from 'react';
-import camelCase from 'lodash.camelcase';
import { LDFlagSet, LDFlagChangeset } from 'launchdarkly-js-client-sdk';
import { defaultReactOptions, ProviderConfig } from './types';
import { Provider } from './context';
import initLDClient from './initLDClient';
-import { camelCaseKeys } from './utils';
+import { camelCaseKeys, getFlattenedFlagsFromChangeset } from './utils';
/**
* This is an async function which initializes LaunchDarkly's JS SDK (`launchdarkly-js-client-sdk`)
@@ -46,14 +45,10 @@ export default async function asyncWithLDProvider(config: ProviderConfig) {
}
ldClient.on('change', (changes: LDFlagChangeset) => {
- const flattened: LDFlagSet = {};
- for (const key in changes) {
- // tslint:disable-next-line:no-unsafe-any
- const flagKey = reactOptions.useCamelCaseFlagKeys ? camelCase(key) : key;
- flattened[flagKey] = changes[key].current;
+ const flattened: LDFlagSet = getFlattenedFlagsFromChangeset(changes, flags, reactOptions);
+ if (Object.keys(flattened).length > 0) {
+ setLDData(prev => ({ ...prev, flags: { ...prev.flags, ...flattened } }));
}
-
- setLDData(prev => ({ ...prev, flags: { ...prev.flags, ...flattened } }));
});
}, []);
diff --git a/src/provider.test.tsx b/src/provider.test.tsx
index 7de1762..4bc7e49 100644
--- a/src/provider.test.tsx
+++ b/src/provider.test.tsx
@@ -311,4 +311,31 @@ describe('LDProvider', () => {
expect(mockInitLDClient).toHaveBeenCalledWith(clientSideID, user, defaultReactOptions, options, undefined);
});
+
+ test('only updates to subscribed flags are pushed to the Provider', async () => {
+ mockInitLDClient.mockImplementation(() => ({
+ flags: { testFlag: 2 },
+ ldClient: mockLDClient,
+ }));
+ mockLDClient.on.mockImplementation((e: string, cb: (c: LDFlagChangeset) => void) => {
+ cb({ 'test-flag': { current: 3, previous: 2 }, 'another-test-flag': { current: false, previous: true } });
+ });
+ const options: LDOptions = {};
+ const user: LDUser = { key: 'yus', name: 'yus ng' };
+ const subscribedFlags = { 'test-flag': 1 };
+ const props: ProviderConfig = { clientSideID, user, options, flags: subscribedFlags };
+ const LaunchDarklyApp = (
+
+
+
+ );
+ const instance = create(LaunchDarklyApp).root.findByType(LDProvider).instance as EnhancedComponent;
+ const mockSetState = jest.spyOn(instance, 'setState');
+
+ await instance.componentDidMount();
+ const callback = mockSetState.mock.calls[1][0] as (flags: LDFlagSet) => LDFlagSet;
+ const newState = callback({});
+
+ expect(newState).toEqual({ flags: { testFlag: 3 } });
+ });
});
diff --git a/src/provider.tsx b/src/provider.tsx
index b4e8717..cceddb9 100644
--- a/src/provider.tsx
+++ b/src/provider.tsx
@@ -1,10 +1,9 @@
import * as React from 'react';
-import camelCase from 'lodash.camelcase';
import { LDClient, LDFlagSet, LDFlagChangeset } from 'launchdarkly-js-client-sdk';
import { EnhancedComponent, ProviderConfig, defaultReactOptions } from './types';
import { Provider, LDContext as HocState } from './context';
import initLDClient from './initLDClient';
-import { camelCaseKeys } from './utils';
+import { camelCaseKeys, getFlattenedFlagsFromChangeset } from './utils';
/**
* The `LDProvider` is a component which accepts a config object which is used to
@@ -52,15 +51,12 @@ class LDProvider extends React.Component implements En
getReactOptions = () => ({ ...defaultReactOptions, ...this.props.reactOptions });
subscribeToChanges = (ldClient: LDClient) => {
+ const { flags: targetFlags } = this.props;
ldClient.on('change', (changes: LDFlagChangeset) => {
- const flattened: LDFlagSet = {};
- for (const key in changes) {
- // tslint:disable-next-line:no-unsafe-any
- const { useCamelCaseFlagKeys } = this.getReactOptions();
- const flagKey = useCamelCaseFlagKeys ? camelCase(key) : key;
- flattened[flagKey] = changes[key].current;
+ const flattened: LDFlagSet = getFlattenedFlagsFromChangeset(changes, targetFlags, this.getReactOptions());
+ if (Object.keys(flattened).length > 0) {
+ this.setState(({ flags }) => ({ flags: { ...flags, ...flattened } }));
}
- this.setState(({ flags }) => ({ flags: { ...flags, ...flattened } }));
});
};
diff --git a/src/utils.test.ts b/src/utils.test.ts
index db45c4c..02bc348 100644
--- a/src/utils.test.ts
+++ b/src/utils.test.ts
@@ -1,4 +1,6 @@
-import { camelCaseKeys } from './utils';
+import { camelCaseKeys, getFlattenedFlagsFromChangeset } from './utils';
+import { LDFlagChangeset, LDFlagSet } from 'launchdarkly-js-client-sdk';
+import { LDReactOptions } from './types';
describe('Utils', () => {
test('camelCaseKeys should ignore system keys', () => {
@@ -15,4 +17,59 @@ describe('Utils', () => {
const result = camelCaseKeys(bootstrap);
expect(result).toEqual({ testFlag: true, anotherTestFlag: false });
});
+
+ test('getFlattenedFlagsFromChangeset should return current values of all flags when no targetFlags specified', () => {
+ const targetFlags: LDFlagSet | undefined = undefined;
+ const flagChanges: LDFlagChangeset = {
+ 'test-flag': { current: true, previous: false },
+ 'another-test-flag': { current: false, previous: true },
+ };
+ const reactOptions: LDReactOptions = {
+ useCamelCaseFlagKeys: true,
+ };
+ const flattened = getFlattenedFlagsFromChangeset(flagChanges, targetFlags, reactOptions);
+
+ expect(flattened).toEqual({ anotherTestFlag: false, testFlag: true });
+ });
+
+ test('getFlattenedFlagsFromChangeset should return current values only of targetFlags when specified', () => {
+ const targetFlags: LDFlagSet | undefined = { 'test-flag': false };
+ const flagChanges: LDFlagChangeset = {
+ 'test-flag': { current: true, previous: false },
+ 'another-test-flag': { current: false, previous: true },
+ };
+ const reactOptions: LDReactOptions = {
+ useCamelCaseFlagKeys: true,
+ };
+ const flattened = getFlattenedFlagsFromChangeset(flagChanges, targetFlags, reactOptions);
+
+ expect(flattened).toEqual({ testFlag: true });
+ });
+
+ test('getFlattenedFlagsFromChangeset should return empty LDFlagSet when no targetFlags are changed ', () => {
+ const targetFlags: LDFlagSet | undefined = { 'test-flag': false };
+ const flagChanges: LDFlagChangeset = {
+ 'another-test-flag': { current: false, previous: true },
+ };
+ const reactOptions: LDReactOptions = {
+ useCamelCaseFlagKeys: true,
+ };
+ const flattened = getFlattenedFlagsFromChangeset(flagChanges, targetFlags, reactOptions);
+
+ expect(Object.keys(flattened)).toHaveLength(0);
+ });
+
+ test('getFlattenedFlagsFromChangeset should not change flags to camelCase when reactOptions.useCamelCaseFlagKeys is false ', () => {
+ const targetFlags: LDFlagSet | undefined = undefined;
+ const flagChanges: LDFlagChangeset = {
+ 'test-flag': { current: true, previous: false },
+ 'another-test-flag': { current: false, previous: true },
+ };
+ const reactOptions: LDReactOptions = {
+ useCamelCaseFlagKeys: false,
+ };
+ const flattened = getFlattenedFlagsFromChangeset(flagChanges, targetFlags, reactOptions);
+
+ expect(flattened).toEqual({ 'another-test-flag': false, 'test-flag': true });
+ });
});
diff --git a/src/utils.ts b/src/utils.ts
index 88e2eec..66da194 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -1,5 +1,6 @@
-import { LDFlagSet } from 'launchdarkly-js-client-sdk';
+import { LDFlagChangeset, LDFlagSet } from 'launchdarkly-js-client-sdk';
import camelCase from 'lodash.camelcase';
+import { LDReactOptions } from './types';
/**
* Transforms a set of flags so that their keys are camelCased. This function ignores
@@ -20,4 +21,31 @@ export const camelCaseKeys = (rawFlags: LDFlagSet) => {
return flags;
};
-export default { camelCaseKeys };
+/**
+ * Gets the flags to pass to the provider from the changeset.
+ *
+ * @param changes the `LDFlagChangeset` from the ldClient onchange handler.
+ * @param targetFlags if targetFlags are specified, changes to other flags are ignored and not returned in the
+ * flattened `LDFlagSet`
+ * @param reactOptions reactOptions.useCamelCaseFlagKeys determines whether to change the flag keys to camelCase
+ * @return an `LDFlagSet` with the current flag values from the LDFlagChangeset filtered by `targetFlags`. The returned
+ * object may be empty `{}` if none of the targetFlags were changed.
+ */
+export const getFlattenedFlagsFromChangeset = (
+ changes: LDFlagChangeset,
+ targetFlags: LDFlagSet | undefined,
+ reactOptions: LDReactOptions,
+): LDFlagSet => {
+ const flattened: LDFlagSet = {};
+ for (const key in changes) {
+ if (targetFlags === undefined || targetFlags[key] !== undefined) {
+ // tslint:disable-next-line:no-unsafe-any
+ const flagKey = reactOptions.useCamelCaseFlagKeys ? camelCase(key) : key;
+ flattened[flagKey] = changes[key].current;
+ }
+ }
+
+ return flattened;
+};
+
+export default { camelCaseKeys, getFlattenedFlagsFromChangeset };