diff --git a/.github/workflows/ci.yml b/.github/workflows/ci-cd.yml
similarity index 98%
rename from .github/workflows/ci.yml
rename to .github/workflows/ci-cd.yml
index bc3a8b1..3870a1f 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci-cd.yml
@@ -23,14 +23,14 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up nodejs
uses: actions/setup-node@v3
with:
- node-version: '16.16.0'
+ node-version: 'lts/*'
cache: 'npm'
- name: npm ci
diff --git a/.nvmrc b/.nvmrc
index f274881..b009dfb 100644
--- a/.nvmrc
+++ b/.nvmrc
@@ -1 +1 @@
-v16.16.0
+lts/*
diff --git a/CHANGES.txt b/CHANGES.txt
index 7f32a73..dd1026b 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,3 +1,9 @@
+1.11.0 (January 15, 2023)
+ - Added new `SplitFactoryProvider` component as replacement for the now deprecated `SplitFactory` component.
+ This new component is a fixed version of the `SplitFactory` component, which is not handling the SDK initialization side-effects in the `componentDidMount` and `componentDidUpdate` methods (commit phase), causing some issues like the SDK not being reinitialized when component props change (Related to issue #11 and #148).
+ The new component also supports server-side rendering. See our documentation for more details: https://help.split.io/hc/en-us/articles/360038825091-React-SDK#server-side-rendering (Related to issue #11 and #109).
+ - Updated internal code to remove a circular dependency and avoid warning messages with tools like PNPM (Related to issue #176).
+
1.10.2 (December 12, 2023)
- Updated @splitsoftware/splitio package to version 10.24.1 that updates localStorage usage to clear cached feature flag definitions before initiating the synchronization process, if the cache was previously synchronized with a different SDK key (i.e., a different environment) or different Split Filter criteria, to avoid using invalid cached data when the SDK is ready from cache.
diff --git a/README.md b/README.md
index a171ed1..53c4b7f 100644
--- a/README.md
+++ b/README.md
@@ -20,7 +20,7 @@ Below is a simple example that describes the instantiation and most basic usage
import React from 'react';
// Import SDK functions
-import { SplitFactory, useSplitTreatments } from '@splitsoftware/splitio-react';
+import { SplitFactoryProvider, useSplitTreatments } from '@splitsoftware/splitio-react';
// Define your config object
const CONFIG = {
@@ -48,10 +48,10 @@ function MyComponent() {
function MyApp() {
return (
- // Use SplitFactory to instantiate the SDK and makes it available to nested components
-
+ // Use SplitFactoryProvider to instantiate the SDK and makes it available to nested components
+
-
+
);
}
```
diff --git a/src/SplitClient.tsx b/src/SplitClient.tsx
index 0e735a8..fa584f4 100644
--- a/src/SplitClient.tsx
+++ b/src/SplitClient.tsx
@@ -1,13 +1,12 @@
import React from 'react';
import { SplitContext } from './SplitContext';
import { ISplitClientProps, ISplitContextValues, IUpdateProps } from './types';
-import { ERROR_SC_NO_FACTORY } from './constants';
import { getStatus, getSplitClient, initAttributes, IClientWithContext } from './utils';
import { DEFAULT_UPDATE_OPTIONS } from './useSplitClient';
/**
* Common component used to handle the status and events of a Split client passed as prop.
- * Reused by both SplitFactory (main client) and SplitClient (shared client) components.
+ * Reused by both SplitFactoryProvider (main client) and SplitClient (any client) components.
*/
export class SplitComponent extends React.Component {
@@ -47,11 +46,6 @@ export class SplitComponent extends React.Component(INITIAL_CONTEXT);
diff --git a/src/SplitFactoryProvider.tsx b/src/SplitFactoryProvider.tsx
index d3ac78b..4ecfd3c 100644
--- a/src/SplitFactoryProvider.tsx
+++ b/src/SplitFactoryProvider.tsx
@@ -3,7 +3,7 @@ import React from 'react';
import { SplitComponent } from './SplitClient';
import { ISplitFactoryProps } from './types';
import { WARN_SF_CONFIG_AND_FACTORY } from './constants';
-import { getSplitFactory, destroySplitFactory, IFactoryWithClients, getSplitClient, getStatus } from './utils';
+import { getSplitFactory, destroySplitFactory, IFactoryWithClients, getSplitClient, getStatus, __factories } from './utils';
import { DEFAULT_UPDATE_OPTIONS } from './useSplitClient';
/**
@@ -27,24 +27,39 @@ export function SplitFactoryProvider(props: ISplitFactoryProps) {
config = undefined;
}
- const [stateFactory, setStateFactory] = React.useState(propFactory || null);
- const factory = propFactory || stateFactory;
+ const [configFactory, setConfigFactory] = React.useState(null);
+ const factory = propFactory || (configFactory && config === configFactory.config ? configFactory : null);
const client = factory ? getSplitClient(factory) : null;
+ // Effect to initialize and destroy the factory
React.useEffect(() => {
if (config) {
const factory = getSplitFactory(config);
+
+ return () => {
+ destroySplitFactory(factory);
+ }
+ }
+ }, [config]);
+
+ // Effect to subscribe/unsubscribe to events
+ React.useEffect(() => {
+ const factory = config && __factories.get(config);
+ if (factory) {
const client = getSplitClient(factory);
const status = getStatus(client);
- // Update state and unsubscribe from events when first event is emitted
- const update = () => {
+ // Unsubscribe from events and update state when first event is emitted
+ const update = () => { // eslint-disable-next-line no-use-before-define
+ unsubscribe();
+ setConfigFactory(factory);
+ }
+
+ const unsubscribe = () => {
client.off(client.Event.SDK_READY, update);
client.off(client.Event.SDK_READY_FROM_CACHE, update);
client.off(client.Event.SDK_READY_TIMED_OUT, update);
client.off(client.Event.SDK_UPDATE, update);
-
- setStateFactory(factory);
}
if (updateOnSdkReady) {
@@ -61,10 +76,7 @@ export function SplitFactoryProvider(props: ISplitFactoryProps) {
}
if (updateOnSdkUpdate) client.on(client.Event.SDK_UPDATE, update);
- return () => {
- // Factory destroy unsubscribes from events
- destroySplitFactory(factory as IFactoryWithClients);
- }
+ return unsubscribe;
}
}, [config, updateOnSdkReady, updateOnSdkReadyFromCache, updateOnSdkTimedout, updateOnSdkUpdate]);
diff --git a/src/SplitTreatments.tsx b/src/SplitTreatments.tsx
index 995f108..da803c3 100644
--- a/src/SplitTreatments.tsx
+++ b/src/SplitTreatments.tsx
@@ -1,7 +1,6 @@
import React from 'react';
import { SplitContext } from './SplitContext';
import { ISplitTreatmentsProps, ISplitContextValues } from './types';
-import { WARN_ST_NO_CLIENT } from './constants';
import { memoizeGetTreatmentsWithConfig } from './utils';
/**
@@ -26,7 +25,7 @@ export class SplitTreatments extends React.Component {
{(splitContext: ISplitContextValues) => {
const { client, lastUpdate } = splitContext;
const treatments = this.evaluateFeatureFlags(client, lastUpdate, names, attributes, client ? { ...client.getAttributes() } : {}, flagSets);
- if (!client) { this.logWarning = true; }
+
// SplitTreatments only accepts a function as a child, not a React Element (JSX)
return children({
...splitContext, treatments,
@@ -35,9 +34,4 @@ export class SplitTreatments extends React.Component {
);
}
-
- componentDidMount() {
- if (this.logWarning) { console.log(WARN_ST_NO_CLIENT); }
- }
-
}
diff --git a/src/__tests__/SplitClient.test.tsx b/src/__tests__/SplitClient.test.tsx
index 482403e..3ab262c 100644
--- a/src/__tests__/SplitClient.test.tsx
+++ b/src/__tests__/SplitClient.test.tsx
@@ -11,17 +11,16 @@ import { sdkBrowser } from './testUtils/sdkConfigs';
/** Test target */
import { ISplitClientChildProps } from '../types';
-import { SplitFactory } from '../SplitFactory';
+import { SplitFactoryProvider } from '../SplitFactoryProvider';
import { SplitClient } from '../SplitClient';
import { SplitContext } from '../SplitContext';
-import { ERROR_SC_NO_FACTORY } from '../constants';
import { testAttributesBinding, TestComponentProps } from './testUtils/utils';
describe('SplitClient', () => {
test('passes no-ready props to the child if client is not ready.', () => {
render(
-
+
{({ isReady, isReadyFromCache, hasTimedout, isTimedout, isDestroyed, lastUpdate }: ISplitClientChildProps) => {
expect(isReady).toBe(false);
@@ -34,7 +33,7 @@ describe('SplitClient', () => {
return null;
}}
-
+
);
});
@@ -46,7 +45,7 @@ describe('SplitClient', () => {
await outerFactory.client().ready();
render(
-
+
{/* Equivalent to */}
{({ client, isReady, isReadyFromCache, hasTimedout, isTimedout, isDestroyed, lastUpdate }: ISplitClientChildProps) => {
@@ -61,7 +60,7 @@ describe('SplitClient', () => {
return null;
}}
-
+
);
});
@@ -76,7 +75,7 @@ describe('SplitClient', () => {
let previousLastUpdate = -1;
render(
-
+
{({ client, isReady, isReadyFromCache, hasTimedout, isTimedout, lastUpdate }: ISplitClientChildProps) => {
const statusProps = [isReady, isReadyFromCache, hasTimedout, isTimedout];
@@ -106,7 +105,7 @@ describe('SplitClient', () => {
return null;
}}
-
+
);
act(() => (outerFactory as any).client('user2').__emitter__.emit(Event.SDK_READY_TIMED_OUT));
@@ -128,7 +127,7 @@ describe('SplitClient', () => {
let previousLastUpdate = -1;
render(
-
+
{({ client, isReady, isReadyFromCache, hasTimedout, isTimedout, lastUpdate }: ISplitClientChildProps) => {
const statusProps = [isReady, isReadyFromCache, hasTimedout, isTimedout];
@@ -152,7 +151,7 @@ describe('SplitClient', () => {
return null;
}}
-
+
);
act(() => (outerFactory as any).client('user2').__emitter__.emit(Event.SDK_READY_TIMED_OUT));
@@ -172,7 +171,7 @@ describe('SplitClient', () => {
let previousLastUpdate = -1;
render(
-
+
{({ client, isReady, isReadyFromCache, hasTimedout, isTimedout, lastUpdate }: ISplitClientChildProps) => {
const statusProps = [isReady, isReadyFromCache, hasTimedout, isTimedout];
@@ -193,7 +192,7 @@ describe('SplitClient', () => {
return null;
}}
-
+
);
act(() => (outerFactory as any).client('user2').__emitter__.emit(Event.SDK_READY_TIMED_OUT));
@@ -207,7 +206,7 @@ describe('SplitClient', () => {
let count = 0;
render(
-
+
{({ client }) => {
count++;
@@ -221,7 +220,7 @@ describe('SplitClient', () => {
return null;
}}
-
+
);
expect(count).toEqual(2);
@@ -246,26 +245,27 @@ describe('SplitClient', () => {
};
render(
-
+
-
+
);
});
- test('logs error and passes null client if rendered outside an SplitProvider component.', () => {
- const errorSpy = jest.spyOn(console, 'error');
- render(
-
- {({ client }) => {
- expect(client).toBe(null);
- return null;
- }}
-
- );
- expect(errorSpy).toBeCalledWith(ERROR_SC_NO_FACTORY);
- });
+ // @TODO Update test in breaking change, following common practice in React libraries, like React-redux and React-query: use a falsy value as default context value, and throw an error – instead of logging it – if components are not wrapped in a SplitContext.Provider, i.e., if the context is falsy.
+ // test('logs error and passes null client if rendered outside an SplitProvider component.', () => {
+ // const errorSpy = jest.spyOn(console, 'error');
+ // render(
+ //
+ // {({ client }) => {
+ // expect(client).toBe(null);
+ // return null;
+ // }}
+ //
+ // );
+ // expect(errorSpy).toBeCalledWith(ERROR_SC_NO_FACTORY);
+ // });
test(`passes a new client if re-rendered with a different splitKey.
Only updates the state if the new client triggers an event, but not the previous one.`, (done) => {
@@ -338,9 +338,9 @@ describe('SplitClient', () => {
}
render(
-
+
-
+
);
});
@@ -348,14 +348,14 @@ describe('SplitClient', () => {
function Component({ attributesFactory, attributesClient, splitKey, testSwitch, factory }: TestComponentProps) {
return (
-
+
{() => {
testSwitch(done, splitKey);
return null;
}}
-
+
);
}
diff --git a/src/__tests__/SplitFactoryProvider.test.tsx b/src/__tests__/SplitFactoryProvider.test.tsx
index 2964a60..6745a4d 100644
--- a/src/__tests__/SplitFactoryProvider.test.tsx
+++ b/src/__tests__/SplitFactoryProvider.test.tsx
@@ -336,40 +336,95 @@ describe('SplitFactoryProvider', () => {
logSpy.mockRestore();
});
- test('cleans up on unmount.', () => {
- let destroyMainClientSpy;
- let destroySharedClientSpy;
- const wrapper = render(
-
- {({ factory }) => {
- if (!factory) return null; // 1st render
+ test('cleans up on update and unmount if config prop is provided.', () => {
+ let renderTimes = 0;
+ const createdFactories = new Set();
+ const clientDestroySpies: jest.SpyInstance[] = [];
+ const outerFactory = SplitSdk(sdkBrowser);
+
+ const Component = ({ factory, isReady, hasTimedout }: ISplitFactoryChildProps) => {
+ renderTimes++;
- // 2nd render (SDK ready)
- expect(__factories.size).toBe(1);
- destroyMainClientSpy = jest.spyOn((factory as SplitIO.ISDK).client(), 'destroy');
+ switch (renderTimes) {
+ case 1:
+ expect(factory).toBe(outerFactory);
+ return null;
+ case 2:
+ case 5:
+ expect(isReady).toBe(false);
+ expect(hasTimedout).toBe(false);
+ expect(factory).toBe(null);
+ return null;
+ case 3:
+ case 4:
+ case 6:
+ expect(isReady).toBe(true);
+ expect(hasTimedout).toBe(true);
+ expect(factory).not.toBe(null);
+ createdFactories.add(factory!);
+ clientDestroySpies.push(jest.spyOn(factory!.client(), 'destroy'));
return (
{({ client }) => {
- destroySharedClientSpy = jest.spyOn(client as SplitIO.IClient, 'destroy');
+ clientDestroySpies.push(jest.spyOn(client!, 'destroy'));
return null;
}}
);
- }}
-
- );
+ case 7:
+ throw new Error('Must not rerender');
+ }
+ };
- // SDK ready to re-render
- act(() => {
+ const emitSdkEvents = () => {
const factory = (SplitSdk as jest.Mock).mock.results.slice(-1)[0].value;
+ factory.client().__emitter__.emit(Event.SDK_READY_TIMED_OUT)
factory.client().__emitter__.emit(Event.SDK_READY)
- });
+ };
+
+ // 1st render: factory provided
+ const wrapper = render(
+
+ {Component}
+
+ );
+
+ // 2nd render: factory created, not ready (null)
+ wrapper.rerender(
+
+ {Component}
+
+ );
+
+ // 3rd render: SDK ready (timeout is ignored due to updateOnSdkTimedout=false)
+ act(emitSdkEvents);
+
+ // 4th render: same config prop -> factory is not recreated
+ wrapper.rerender(
+
+ {Component}
+
+ );
+
+ act(emitSdkEvents); // Emitting events again has no effect
+ expect(createdFactories.size).toBe(1);
+
+ // 5th render: Update config prop -> factory is recreated, not ready yet (null)
+ wrapper.rerender(
+
+ {Component}
+
+ );
+
+ // 6th render: SDK timeout (ready is ignored due to updateOnSdkReady=false)
+ act(emitSdkEvents);
wrapper.unmount();
- // the factory created by the component is removed from `factories` cache and its clients are destroyed
+
+ // Created factories are removed from `factories` cache and their clients are destroyed
+ expect(createdFactories.size).toBe(2);
expect(__factories.size).toBe(0);
- expect(destroyMainClientSpy).toBeCalledTimes(1);
- expect(destroySharedClientSpy).toBeCalledTimes(1);
+ clientDestroySpies.forEach(spy => expect(spy).toBeCalledTimes(1));
});
test('doesn\'t clean up on unmount if the factory is provided as a prop.', () => {
@@ -381,11 +436,11 @@ describe('SplitFactoryProvider', () => {
{({ factory }) => {
// if factory is provided as a prop, `factories` cache is not modified
expect(__factories.size).toBe(0);
- destroyMainClientSpy = jest.spyOn((factory as SplitIO.ISDK).client(), 'destroy');
+ destroyMainClientSpy = jest.spyOn(factory!.client(), 'destroy');
return (
{({ client }) => {
- destroySharedClientSpy = jest.spyOn(client as SplitIO.IClient, 'destroy');
+ destroySharedClientSpy = jest.spyOn(client!, 'destroy');
return null;
}}
diff --git a/src/__tests__/SplitTreatments.test.tsx b/src/__tests__/SplitTreatments.test.tsx
index 13d8990..308379c 100644
--- a/src/__tests__/SplitTreatments.test.tsx
+++ b/src/__tests__/SplitTreatments.test.tsx
@@ -10,13 +10,13 @@ import { SplitFactory as SplitSdk } from '@splitsoftware/splitio/client';
import { sdkBrowser } from './testUtils/sdkConfigs';
import { getStatus } from '../utils';
import { newSplitFactoryLocalhostInstance } from './testUtils/utils';
-import { CONTROL_WITH_CONFIG, WARN_ST_NO_CLIENT } from '../constants';
+import { CONTROL_WITH_CONFIG } from '../constants';
/** Test target */
import { ISplitTreatmentsChildProps, ISplitTreatmentsProps, ISplitClientProps } from '../types';
import { SplitTreatments } from '../SplitTreatments';
import { SplitClient } from '../SplitClient';
-import { SplitFactory } from '../SplitFactory';
+import { SplitFactoryProvider } from '../SplitFactoryProvider';
import { useSplitTreatments } from '../useSplitTreatments';
const logSpy = jest.spyOn(console, 'log');
@@ -28,24 +28,20 @@ describe('SplitTreatments', () => {
afterEach(() => { logSpy.mockClear() });
- it('passes control treatments (empty object if flagSets is provided) if the SDK is not ready.', () => {
+ it('passes control treatments (empty object if flagSets is provided) if the SDK is not operational.', () => {
render(
-
- {({ factory }) => {
+
+ {() => {
return (
{({ treatments }: ISplitTreatmentsChildProps) => {
- const clientMock: any = factory?.client('user1');
- expect(clientMock.getTreatmentsWithConfig).not.toBeCalled();
expect(treatments).toEqual({ split1: CONTROL_WITH_CONFIG, split2: CONTROL_WITH_CONFIG });
return null;
}}
{({ treatments }: ISplitTreatmentsChildProps) => {
- const clientMock: any = factory?.client('user1');
- expect(clientMock.getTreatmentsWithConfigByFlagSets).not.toBeCalled();
expect(treatments).toEqual({});
return null;
}}
@@ -53,7 +49,7 @@ describe('SplitTreatments', () => {
);
}}
-
+
);
});
@@ -62,7 +58,7 @@ describe('SplitTreatments', () => {
(outerFactory as any).client().__emitter__.emit(Event.SDK_READY);
render(
-
+
{({ factory, isReady }) => {
expect(getStatus(outerFactory.client()).isReady).toBe(isReady);
expect(isReady).toBe(true);
@@ -90,22 +86,23 @@ describe('SplitTreatments', () => {
>
);
}}
-
+
);
});
- it('logs error and passes control treatments if rendered outside an SplitProvider component.', () => {
- render(
-
- {({ treatments }: ISplitTreatmentsChildProps) => {
- expect(treatments).toEqual({ split1: CONTROL_WITH_CONFIG, split2: CONTROL_WITH_CONFIG });
- return null;
- }}
-
- );
+ // @TODO Update test in breaking change, following common practice in React libraries, like React-redux and React-query: use a falsy value as default context value, and throw an error – instead of logging it – if components are not wrapped in a SplitContext.Provider, i.e., if the context is falsy.
+ // it('logs error and passes control treatments if rendered outside an SplitProvider component.', () => {
+ // render(
+ //
+ // {({ treatments }: ISplitTreatmentsChildProps) => {
+ // expect(treatments).toEqual({ split1: CONTROL_WITH_CONFIG, split2: CONTROL_WITH_CONFIG });
+ // return null;
+ // }}
+ //
+ // );
- expect(logSpy).toBeCalledWith(WARN_ST_NO_CLIENT);
- });
+ // expect(logSpy).toBeCalledWith(WARN_ST_NO_CLIENT);
+ // });
/**
* Input validation. Passing invalid feature flag names or attributes while the Sdk
@@ -113,7 +110,7 @@ describe('SplitTreatments', () => {
*/
it('Input validation: invalid "names" and "attributes" props in SplitTreatments.', (done) => {
render(
-
+
{() => {
return (
<>
@@ -141,7 +138,7 @@ describe('SplitTreatments', () => {
>
);
}}
-
+
);
expect(logSpy).toBeCalledWith('[ERROR] feature flag names must be a non-empty array.');
expect(logSpy).toBeCalledWith('[ERROR] you passed an invalid feature flag name, feature flag name must be a non-empty string.');
@@ -198,11 +195,11 @@ describe.each([
clientAttributes?: ISplitClientProps['attributes']
}) {
return (
-
+
-
+
);
}
@@ -302,27 +299,27 @@ describe.each([
expect(outerFactory.client('otherKey').getTreatmentsWithConfig).toBeCalledTimes(1);
});
- it('rerenders and re-evaluate feature flags when Split context changes (in both SplitFactory and SplitClient components).', async () => {
+ it('rerenders and re-evaluate feature flags when Split context changes (in both SplitFactoryProvider and SplitClient components).', async () => {
// changes in SplitContext implies that either the factory, the client (user key), or its status changed, what might imply a change in treatments
const outerFactory = SplitSdk(sdkBrowser);
let renderTimesComp1 = 0;
let renderTimesComp2 = 0;
- // test context updates on SplitFactory
+ // test context updates on SplitFactoryProvider
render(
-
+
{() => {
renderTimesComp1++;
return null;
}}
-
+
);
// test context updates on SplitClient
render(
-
+
{() => {
@@ -331,7 +328,7 @@ describe.each([
}}
-
+
);
expect(renderTimesComp1).toBe(1);
diff --git a/src/__tests__/useSplitClient.test.tsx b/src/__tests__/useSplitClient.test.tsx
index a4a32e8..29b894e 100644
--- a/src/__tests__/useSplitClient.test.tsx
+++ b/src/__tests__/useSplitClient.test.tsx
@@ -11,23 +11,23 @@ import { sdkBrowser } from './testUtils/sdkConfigs';
/** Test target */
import { useSplitClient } from '../useSplitClient';
-import { SplitFactory } from '../SplitFactory';
+import { SplitFactoryProvider } from '../SplitFactoryProvider';
import { SplitClient } from '../SplitClient';
import { SplitContext } from '../SplitContext';
import { testAttributesBinding, TestComponentProps } from './testUtils/utils';
describe('useSplitClient', () => {
- test('returns the main client from the context updated by SplitFactory.', () => {
+ test('returns the main client from the context updated by SplitFactoryProvider.', () => {
const outerFactory = SplitSdk(sdkBrowser);
let client;
render(
-
+
{React.createElement(() => {
client = useSplitClient().client;
return null;
})}
-
+
);
expect(client).toBe(outerFactory.client());
});
@@ -36,14 +36,14 @@ describe('useSplitClient', () => {
const outerFactory = SplitSdk(sdkBrowser);
let client;
render(
-
+
{React.createElement(() => {
client = useSplitClient().client;
return null;
})}
-
+
);
expect(client).toBe(outerFactory.client('user2'));
});
@@ -52,13 +52,13 @@ describe('useSplitClient', () => {
const outerFactory = SplitSdk(sdkBrowser);
let client;
render(
-
+
{React.createElement(() => {
(outerFactory.client as jest.Mock).mockClear();
client = useSplitClient({ splitKey: 'user2', trafficType: 'user' }).client;
return null;
})}
-
+
);
expect(outerFactory.client as jest.Mock).toBeCalledWith('user2', 'user');
expect(outerFactory.client as jest.Mock).toHaveReturnedWith(client);
@@ -89,9 +89,9 @@ describe('useSplitClient', () => {
function Component({ attributesFactory, attributesClient, splitKey, testSwitch, factory }: TestComponentProps) {
return (
-
+
-
+
);
}
@@ -108,13 +108,13 @@ describe('useSplitClient', () => {
let countNestedComponent = 0;
render(
-
+
<>
{() => countSplitContext++}
{() => { countSplitClient++; return null }}
@@ -122,7 +122,7 @@ describe('useSplitClient', () => {
{React.createElement(() => {
// Equivalent to
// - Using config key and traffic type: `const { client } = useSplitClient(sdkBrowser.core.key, sdkBrowser.core.trafficType, { att1: 'att1' });`
- // - Disabling update props, since the wrapping SplitFactory has them enabled: `const { client } = useSplitClient(undefined, undefined, { att1: 'att1' }, { updateOnSdkReady: false, updateOnSdkReadyFromCache: false });`
+ // - Disabling update props, since the wrapping SplitFactoryProvider has them enabled: `const { client } = useSplitClient(undefined, undefined, { att1: 'att1' }, { updateOnSdkReady: false, updateOnSdkReadyFromCache: false });`
const { client } = useSplitClient({ attributes: { att1: 'att1' } });
expect(client).toBe(mainClient); // Assert that the main client was retrieved.
expect(client!.getAttributes()).toEqual({ att1: 'att1' }); // Assert that the client was retrieved with the provided attributes.
@@ -183,7 +183,7 @@ describe('useSplitClient', () => {
})}
>
-
+
);
act(() => mainClient.__emitter__.emit(Event.SDK_READY_FROM_CACHE));
@@ -226,7 +226,7 @@ describe('useSplitClient', () => {
let count = 0;
render(
-
+
{React.createElement(() => {
useSplitClient({ splitKey: 'some_user' });
count++;
@@ -237,7 +237,7 @@ describe('useSplitClient', () => {
return null;
})}
-
+
)
expect(count).toEqual(2);
@@ -257,9 +257,9 @@ describe('useSplitClient', () => {
function Component(updateOptions) {
return (
-
+
-
+
)
}
diff --git a/src/__tests__/useSplitManager.test.tsx b/src/__tests__/useSplitManager.test.tsx
index 9ec5367..4705a48 100644
--- a/src/__tests__/useSplitManager.test.tsx
+++ b/src/__tests__/useSplitManager.test.tsx
@@ -11,7 +11,7 @@ import { sdkBrowser } from './testUtils/sdkConfigs';
import { getStatus } from '../utils';
/** Test target */
-import { SplitFactory } from '../SplitFactory';
+import { SplitFactoryProvider } from '../SplitFactoryProvider';
import { useSplitManager } from '../useSplitManager';
describe('useSplitManager', () => {
@@ -20,12 +20,12 @@ describe('useSplitManager', () => {
const outerFactory = SplitSdk(sdkBrowser);
let hookResult;
render(
-
+
{React.createElement(() => {
hookResult = useSplitManager();
return null;
})}
-
+
);
expect(hookResult).toStrictEqual({
diff --git a/src/__tests__/useSplitTreatments.test.tsx b/src/__tests__/useSplitTreatments.test.tsx
index cbdc62d..13a54b6 100644
--- a/src/__tests__/useSplitTreatments.test.tsx
+++ b/src/__tests__/useSplitTreatments.test.tsx
@@ -11,7 +11,7 @@ import { sdkBrowser } from './testUtils/sdkConfigs';
import { CONTROL_WITH_CONFIG } from '../constants';
/** Test target */
-import { SplitFactory } from '../SplitFactory';
+import { SplitFactoryProvider } from '../SplitFactoryProvider';
import { SplitClient } from '../SplitClient';
import { useSplitTreatments } from '../useSplitTreatments';
import { SplitTreatments } from '../SplitTreatments';
@@ -33,7 +33,7 @@ describe('useSplitTreatments', () => {
let treatmentsByFlagSets: SplitIO.TreatmentsWithConfig;
render(
-
+
{React.createElement(() => {
treatments = useSplitTreatments({ names: featureFlagNames, attributes }).treatments;
treatmentsByFlagSets = useSplitTreatments({ flagSets, attributes }).treatments;
@@ -42,7 +42,7 @@ describe('useSplitTreatments', () => {
expect(useSplitTreatments({}).treatments).toEqual({});
return null;
})}
-
+
);
// returns control treatment if not operational (SDK not ready or destroyed), without calling `getTreatmentsWithConfig` method
@@ -69,14 +69,14 @@ describe('useSplitTreatments', () => {
let treatments: SplitIO.TreatmentsWithConfig;
render(
-
+
{React.createElement(() => {
treatments = useSplitTreatments({ names: featureFlagNames, attributes }).treatments;
return null;
})}
-
+
);
// returns control treatment if not operational (SDK not ready or destroyed), without calling `getTreatmentsWithConfig` method
@@ -96,7 +96,7 @@ describe('useSplitTreatments', () => {
let renderTimes = 0;
render(
-
+
{React.createElement(() => {
const treatments = useSplitTreatments({ names: featureFlagNames, attributes, splitKey: 'user2' }).treatments;
@@ -119,7 +119,7 @@ describe('useSplitTreatments', () => {
return null;
})}
-
+
);
act(() => client.__emitter__.emit(Event.SDK_READY_FROM_CACHE));
@@ -189,7 +189,7 @@ describe('useSplitTreatments', () => {
}
render(
-
+
<>
{() => countSplitContext++}
@@ -219,7 +219,7 @@ describe('useSplitTreatments', () => {
return null;
})}
>
-
+
);
act(() => mainClient.__emitter__.emit(Event.SDK_READY_FROM_CACHE));
diff --git a/src/__tests__/useTrack.test.tsx b/src/__tests__/useTrack.test.tsx
index 11ecce4..4447189 100644
--- a/src/__tests__/useTrack.test.tsx
+++ b/src/__tests__/useTrack.test.tsx
@@ -10,7 +10,7 @@ import { SplitFactory as SplitSdk } from '@splitsoftware/splitio/client';
import { sdkBrowser } from './testUtils/sdkConfigs';
/** Test target */
-import { SplitFactory } from '../SplitFactory';
+import { SplitFactoryProvider } from '../SplitFactoryProvider';
import { SplitClient } from '../SplitClient';
import { useTrack } from '../useTrack';
@@ -21,19 +21,19 @@ describe('useTrack', () => {
const value = 10;
const properties = { prop1: 'prop1' };
- test('returns the track method bound to the client at Split context updated by SplitFactory.', () => {
+ test('returns the track method bound to the client at Split context updated by SplitFactoryProvider.', () => {
const outerFactory = SplitSdk(sdkBrowser);
let boundTrack;
let trackResult;
render(
-
+
{React.createElement(() => {
boundTrack = useTrack();
trackResult = boundTrack(tt, eventType, value, properties);
return null;
})}
- ,
+ ,
);
const track = outerFactory.client().track as jest.Mock;
expect(track).toBeCalledWith(tt, eventType, value, properties);
@@ -46,7 +46,7 @@ describe('useTrack', () => {
let trackResult;
render(
-
+
{React.createElement(() => {
boundTrack = useTrack();
@@ -54,7 +54,7 @@ describe('useTrack', () => {
return null;
})}
-
+
);
const track = outerFactory.client('user2').track as jest.Mock;
expect(track).toBeCalledWith(tt, eventType, value, properties);
@@ -67,13 +67,13 @@ describe('useTrack', () => {
let trackResult;
render(
-
+
{React.createElement(() => {
boundTrack = useTrack('user2', tt);
trackResult = boundTrack(eventType, value, properties);
return null;
})}
- ,
+ ,
);
const track = outerFactory.client('user2', tt).track as jest.Mock;
expect(track).toBeCalledWith(eventType, value, properties);
diff --git a/src/__tests__/withSplitFactory.test.tsx b/src/__tests__/withSplitFactory.test.tsx
index 32813b0..6df93f6 100644
--- a/src/__tests__/withSplitFactory.test.tsx
+++ b/src/__tests__/withSplitFactory.test.tsx
@@ -15,7 +15,7 @@ jest.mock('../SplitFactory');
import { ISplitFactoryChildProps } from '../types';
import { withSplitFactory } from '../withSplitFactory';
-describe('SplitFactory', () => {
+describe('withSplitFactory', () => {
test('passes no-ready props to the child if initialized with a no ready factory (e.g., using config object).', () => {
const Component = withSplitFactory(sdkBrowser)(
diff --git a/src/constants.ts b/src/constants.ts
index 7d81db7..c29d802 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -14,14 +14,11 @@ export const CONTROL_WITH_CONFIG: SplitIO.TreatmentWithConfig = {
};
// Warning and error messages
-export const WARN_SF_CONFIG_AND_FACTORY: string = '[WARN] Both a config and factory props were provided to SplitFactory. Config prop will be ignored.';
+export const WARN_SF_CONFIG_AND_FACTORY: string = '[WARN] Both a config and factory props were provided to SplitFactoryProvider. Config prop will be ignored.';
+// @TODO remove with SplitFactory component in next major version. SplitFactoryProvider can accept no props and eventually only an initialState
export const ERROR_SF_NO_CONFIG_AND_FACTORY: string = '[ERROR] SplitFactory must receive either a Split config or a Split factory as props.';
-export const ERROR_SC_NO_FACTORY: string = '[ERROR] SplitClient does not have access to a Split factory. This is because it is not inside the scope of a SplitFactory component or SplitFactory was not properly instantiated.';
-
-export const WARN_ST_NO_CLIENT: string = '[WARN] SplitTreatments does not have access to a Split client. This is because it is not inside the scope of a SplitFactory component or SplitFactory was not properly instantiated.';
-
export const EXCEPTION_NO_REACT_OR_CREATECONTEXT: string = 'React library is not available or its version is not supported. Check that it is properly installed or imported. Split SDK requires version 16.3.0+ of React.';
export const WARN_NAMES_AND_FLAGSETS: string = '[WARN] Both names and flagSets properties were provided. flagSets will be ignored.';
diff --git a/src/types.ts b/src/types.ts
index 83d4b26..5fc7cac 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -45,12 +45,17 @@ export interface ISplitStatus {
export interface ISplitContextValues extends ISplitStatus {
/**
- * Split factory instance
+ * Split factory instance.
+ *
+ * NOTE: This property is not recommended for direct use, as better alternatives are available.
*/
factory: SplitIO.IBrowserSDK | null;
/**
- * Split client instance
+ * Split client instance.
+ *
+ * NOTE: This property is not recommended for direct use, as better alternatives are available.
+ *
* @see {@link https://help.split.io/hc/en-us/articles/360020448791-JavaScript-SDK#2-instantiate-the-sdk-and-create-a-new-split-client}
*/
client: SplitIO.IBrowserClient | null;
@@ -94,14 +99,14 @@ export interface IUpdateProps {
}
/**
- * SplitFactory Child Props interface. These are the props that the child component receives from the 'SplitFactory' component.
+ * SplitFactoryProvider Child Props interface. These are the props that the child component receives from the 'SplitFactoryProvider' component.
*/
-// @TODO remove next type (breaking-change)
+// @TODO rename/remove next type (breaking-change)
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface ISplitFactoryChildProps extends ISplitContextValues { }
/**
- * SplitFactory Props interface. These are the props accepted by SplitFactory component,
+ * SplitFactoryProvider Props interface. These are the props accepted by SplitFactoryProvider component,
* used to instantiate a factory and client instance, update the Split context, and listen for SDK events.
*/
export interface ISplitFactoryProps extends IUpdateProps {
@@ -114,6 +119,8 @@ export interface ISplitFactoryProps extends IUpdateProps {
/**
* Split factory instance to use instead of creating a new one with the config object.
+ *
+ * If both `factory` and `config` are provided, the `config` option is ignored.
*/
factory?: SplitIO.IBrowserSDK;
@@ -123,7 +130,7 @@ export interface ISplitFactoryProps extends IUpdateProps {
attributes?: SplitIO.Attributes;
/**
- * Children of the SplitFactory component. It can be a functional component (child as a function) or a React element.
+ * Children of the SplitFactoryProvider component. It can be a functional component (child as a function) or a React element.
*/
children: ((props: ISplitFactoryChildProps) => ReactNode) | ReactNode;
}
@@ -165,7 +172,7 @@ export interface ISplitClientChildProps extends ISplitContextValues { }
export interface ISplitClientProps extends IUseSplitClientOptions {
/**
- * Children of the SplitFactory component. It can be a functional component (child as a function) or a React element.
+ * Children of the SplitClient component. It can be a functional component (child as a function) or a React element.
*/
children: ((props: ISplitClientChildProps) => ReactNode) | ReactNode;
}
diff --git a/src/useClient.ts b/src/useClient.ts
index 535eb98..7428b60 100644
--- a/src/useClient.ts
+++ b/src/useClient.ts
@@ -3,9 +3,9 @@ import { useSplitClient } from './useSplitClient';
/**
* 'useClient' is a hook that returns a client from the Split context.
* It uses the 'useContext' hook to access the context, which is updated by
- * SplitFactory and SplitClient components in the hierarchy of components.
+ * SplitFactoryProvider and SplitClient components in the hierarchy of components.
*
- * @returns A Split Client instance, or null if used outside the scope of SplitFactory
+ * @returns A Split Client instance, or null if used outside the scope of SplitFactoryProvider or factory is not ready.
*
* @see {@link https://help.split.io/hc/en-us/articles/360020448791-JavaScript-SDK#advanced-instantiate-multiple-sdk-clients}
*
diff --git a/src/useManager.ts b/src/useManager.ts
index 9ba101b..094ec99 100644
--- a/src/useManager.ts
+++ b/src/useManager.ts
@@ -3,9 +3,9 @@ import { useSplitManager } from './useSplitManager';
/**
* 'useManager' is a hook that returns the Manager instance from the Split factory.
* It uses the 'useContext' hook to access the factory at Split context, which is updated by
- * the SplitFactory component.
+ * the SplitFactoryProvider component.
*
- * @returns A Split Manager instance, or null if used outside the scope of SplitFactory
+ * @returns A Split Manager instance, or null if used outside the scope of SplitFactoryProvider or factory is not ready.
*
* @see {@link https://help.split.io/hc/en-us/articles/360020448791-JavaScript-SDK#manager}
*
diff --git a/src/useSplitClient.ts b/src/useSplitClient.ts
index 3ff8562..5428aa7 100644
--- a/src/useSplitClient.ts
+++ b/src/useSplitClient.ts
@@ -12,7 +12,7 @@ export const DEFAULT_UPDATE_OPTIONS = {
/**
* 'useSplitClient' is a hook that returns an Split Context object with the client and its status corresponding to the provided key and trafficType.
- * It uses the 'useContext' hook to access the context, which is updated by SplitFactory and SplitClient components in the hierarchy of components.
+ * It uses the 'useContext' hook to access the context, which is updated by SplitFactoryProvider and SplitClient components in the hierarchy of components.
*
* @returns A Split Context object
*
diff --git a/src/useSplitManager.ts b/src/useSplitManager.ts
index 1ba6a23..13aa9b1 100644
--- a/src/useSplitManager.ts
+++ b/src/useSplitManager.ts
@@ -4,9 +4,9 @@ import { ISplitContextValues } from './types';
/**
* 'useSplitManager' is a hook that returns an Split Context object with the Manager instance from the Split factory.
- * It uses the 'useContext' hook to access the factory at Split context, which is updated by the SplitFactory component.
+ * It uses the 'useContext' hook to access the factory at Split context, which is updated by the SplitFactoryProvider component.
*
- * @returns An object containing the Split context and the Split Manager instance, which is null if used outside the scope of SplitFactory
+ * @returns An object containing the Split context and the Split Manager instance, which is null if used outside the scope of SplitFactoryProvider or factory is not ready.
*
* @example
* ```js
@@ -16,7 +16,7 @@ import { ISplitContextValues } from './types';
* @see {@link https://help.split.io/hc/en-us/articles/360020448791-JavaScript-SDK#manager}
*/
export function useSplitManager(): ISplitContextValues & { manager: SplitIO.IManager | null } {
- // Update options are not supported, because updates can be controlled at the SplitFactory component.
+ // Update options are not supported, because updates can be controlled at the SplitFactoryProvider component.
const context = React.useContext(SplitContext);
return {
...context,