Skip to content

Commit 8bdaf82

Browse files
Merge pull request #235 from splitio/default_options
Support overwriting default options
2 parents 69ca89e + c5def42 commit 8bdaf82

12 files changed

+124
-75
lines changed

CHANGES.txt

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
2.1.2 (April XX, 2025)
1+
2.2.0 (April 15, 2025)
2+
- Added `updateOnSdkUpdate`, `updateOnSdkReady`, `updateOnSdkReadyFromCache` and `updateOnSdkTimedout` props to the `SplitFactoryProvider` component to overwrite the default value (`true`) of the `updateOnSdk<Event>` options in the `useSplitClient` and `useSplitTreatments` hooks.
23
- Updated development dependencies to use React v19 and TypeScript v4.5.5 to test compatibility and avoid type conflicts when using the SDK with React v19 types.
34

45
2.1.1 (April 9, 2025)

package-lock.json

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@splitsoftware/splitio-react",
3-
"version": "2.1.1",
3+
"version": "2.2.0",
44
"description": "A React library to easily integrate and use Split JS SDK",
55
"main": "cjs/index.js",
66
"module": "esm/index.js",

src/SplitFactoryProvider.tsx

+8-2
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@ import { SplitFactory } from '@splitsoftware/splitio/client';
2727
* @see {@link https://help.split.io/hc/en-us/articles/360038825091-React-SDK#2-instantiate-the-sdk-and-create-a-new-split-client}
2828
*/
2929
export function SplitFactoryProvider(props: ISplitFactoryProviderProps) {
30-
const { config, factory: propFactory, attributes } = props;
30+
const {
31+
config, factory: propFactory, attributes,
32+
updateOnSdkReady = true, updateOnSdkReadyFromCache = true, updateOnSdkTimedout = true, updateOnSdkUpdate = true
33+
} = props;
3134

3235
const factory = React.useMemo<undefined | SplitIO.IBrowserSDK & { init?: () => void }>(() => {
3336
return propFactory ?
@@ -62,7 +65,10 @@ export function SplitFactoryProvider(props: ISplitFactoryProviderProps) {
6265
}, [config, propFactory, factory]);
6366

6467
return (
65-
<SplitContext.Provider value={{ factory, client, ...getStatus(client) }} >
68+
<SplitContext.Provider value={{
69+
factory, client, ...getStatus(client),
70+
updateOnSdkReady, updateOnSdkReadyFromCache, updateOnSdkTimedout, updateOnSdkUpdate
71+
}} >
6672
{props.children}
6773
</SplitContext.Provider>
6874
);

src/__tests__/SplitClient.test.tsx

+26-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { sdkBrowser } from './testUtils/sdkConfigs';
1313
import { ISplitClientChildProps, ISplitFactoryChildProps } from '../types';
1414
import { SplitFactoryProvider } from '../SplitFactoryProvider';
1515
import { SplitClient } from '../SplitClient';
16-
import { SplitContext } from '../SplitContext';
16+
import { SplitContext, useSplitContext } from '../SplitContext';
1717
import { INITIAL_STATUS, testAttributesBinding, TestComponentProps } from './testUtils/utils';
1818
import { getStatus } from '../utils';
1919
import { EXCEPTION_NO_SFP } from '../constants';
@@ -360,6 +360,31 @@ describe('SplitClient', () => {
360360
testAttributesBinding(Component);
361361
});
362362

363+
364+
test('must overwrite `updateOn<Event>` options in context', () => {
365+
render(
366+
<SplitFactoryProvider updateOnSdkReady={true} updateOnSdkReadyFromCache={false} updateOnSdkTimedout={undefined} >
367+
{React.createElement(() => {
368+
expect(useSplitContext()).toEqual({
369+
...INITIAL_STATUS,
370+
updateOnSdkReadyFromCache: false
371+
});
372+
return null;
373+
})}
374+
<SplitClient updateOnSdkReady={false} updateOnSdkReadyFromCache={undefined} updateOnSdkTimedout={false} >
375+
{React.createElement(() => {
376+
expect(useSplitContext()).toEqual({
377+
...INITIAL_STATUS,
378+
updateOnSdkReady: false,
379+
updateOnSdkReadyFromCache: false,
380+
updateOnSdkTimedout: false
381+
});
382+
return null;
383+
})}
384+
</SplitClient>
385+
</SplitFactoryProvider>
386+
);
387+
});
363388
});
364389

365390
// Tests to validate the migration from `SplitFactoryProvider` with child as a function in v1, to `SplitFactoryProvider` + `SplitClient` with child as a function in v2.

src/__tests__/SplitTreatments.test.tsx

+5-5
Original file line numberDiff line numberDiff line change
@@ -385,33 +385,33 @@ describe.each([
385385
});
386386

387387
expect(renderTimesComp1).toBe(2);
388-
expect(renderTimesComp2).toBe(2); // updateOnSdkReadyFromCache === false, in second component
388+
expect(renderTimesComp2).toBe(1); // updateOnSdkReadyFromCache === false, in second component
389389

390390
act(() => {
391391
(outerFactory as any).client().__emitter__.emit(Event.SDK_READY_TIMED_OUT);
392392
(outerFactory as any).client('user2').__emitter__.emit(Event.SDK_READY_TIMED_OUT);
393393
});
394394

395395
expect(renderTimesComp1).toBe(3);
396-
expect(renderTimesComp2).toBe(3);
396+
expect(renderTimesComp2).toBe(2);
397397

398398
act(() => {
399399
(outerFactory as any).client().__emitter__.emit(Event.SDK_READY);
400400
(outerFactory as any).client('user2').__emitter__.emit(Event.SDK_READY);
401401
});
402402

403403
expect(renderTimesComp1).toBe(3); // updateOnSdkReady === false, in first component
404-
expect(renderTimesComp2).toBe(4);
404+
expect(renderTimesComp2).toBe(3);
405405

406406
act(() => {
407407
(outerFactory as any).client().__emitter__.emit(Event.SDK_UPDATE);
408408
(outerFactory as any).client('user2').__emitter__.emit(Event.SDK_UPDATE);
409409
});
410410

411411
expect(renderTimesComp1).toBe(4);
412-
expect(renderTimesComp2).toBe(5);
412+
expect(renderTimesComp2).toBe(4);
413413
expect(outerFactory.client().getTreatmentsWithConfig).toBeCalledTimes(3); // renderTimes - 1, for the 1st render where SDK is not operational
414-
expect(outerFactory.client('user2').getTreatmentsWithConfig).toBeCalledTimes(4); // idem
414+
expect(outerFactory.client('user2').getTreatmentsWithConfig).toBeCalledTimes(3); // idem
415415
});
416416

417417
it('rerenders and re-evaluates feature flags if client attributes changes.', (done) => {

src/__tests__/testUtils/utils.tsx

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as React from 'react';
22
import { render } from '@testing-library/react';
3-
import { ISplitStatus } from '../../types';
3+
import { ISplitStatus, IUpdateProps } from '../../types';
44
const { SplitFactory: originalSplitFactory } = jest.requireActual('@splitsoftware/splitio/client');
55

66
export interface TestComponentProps {
@@ -116,11 +116,15 @@ export function testAttributesBinding(Component: React.FunctionComponent<TestCom
116116
wrapper.rerender(<Component splitKey={undefined} attributesFactory={undefined} attributesClient={undefined} testSwitch={attributesBindingSwitch} factory={factory} />);
117117
}
118118

119-
export const INITIAL_STATUS: ISplitStatus = {
119+
export const INITIAL_STATUS: ISplitStatus & IUpdateProps = {
120120
isReady: false,
121121
isReadyFromCache: false,
122122
isTimedout: false,
123123
hasTimedout: false,
124124
lastUpdate: 0,
125125
isDestroyed: false,
126+
updateOnSdkReady: true,
127+
updateOnSdkReadyFromCache: true,
128+
updateOnSdkTimedout: true,
129+
updateOnSdkUpdate: true,
126130
}

src/__tests__/useSplitClient.test.tsx

+24-3
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ describe('useSplitClient', () => {
244244

245245
act(() => mainClient.__emitter__.emit(Event.SDK_READY_TIMED_OUT)); // do not trigger re-render because updateOnSdkTimedout is false
246246
expect(rendersCount).toBe(1);
247-
expect(currentStatus).toMatchObject(INITIAL_STATUS);
247+
expect(currentStatus).toMatchObject({ ...INITIAL_STATUS, updateOnSdkUpdate: false, updateOnSdkTimedout: false });
248248

249249
wrapper.rerender(<Component updateOnSdkUpdate={false} updateOnSdkTimedout={false} />);
250250
expect(rendersCount).toBe(2);
@@ -263,7 +263,7 @@ describe('useSplitClient', () => {
263263

264264
wrapper.rerender(<Component updateOnSdkUpdate={false} updateOnSdkTimedout={false} />); // should not update the status (SDK_UPDATE event should be ignored)
265265
expect(rendersCount).toBe(6);
266-
expect(currentStatus).toEqual(previousStatus);
266+
expect(currentStatus).toEqual({ ...previousStatus, updateOnSdkTimedout: false });
267267

268268
wrapper.rerender(<Component updateOnSdkUpdate={null /** invalid type should default to `true` */} />); // trigger re-render and update the status because updateOnSdkUpdate is true and there was an SDK_UPDATE event
269269
expect(rendersCount).toBe(8); // @TODO optimize `useSplitClient` to avoid double render
@@ -275,10 +275,31 @@ describe('useSplitClient', () => {
275275

276276
wrapper.rerender(<Component updateOnSdkUpdate={false} />);
277277
expect(rendersCount).toBe(10);
278-
expect(currentStatus).toEqual(previousStatus);
278+
expect(currentStatus).toEqual({ ...previousStatus, updateOnSdkUpdate: false });
279279

280280
act(() => mainClient.__emitter__.emit(Event.SDK_UPDATE)); // do not trigger re-render because updateOnSdkUpdate is false now
281281
expect(rendersCount).toBe(10);
282282
});
283283

284+
test('must prioritize explicitly provided `updateOn<Event>` options over context defaults', () => {
285+
render(
286+
<SplitFactoryProvider updateOnSdkReady={true} updateOnSdkReadyFromCache={false} updateOnSdkTimedout={undefined} >
287+
{React.createElement(() => {
288+
expect(useSplitClient()).toEqual({
289+
...INITIAL_STATUS,
290+
updateOnSdkReadyFromCache: false
291+
});
292+
293+
expect(useSplitClient({ updateOnSdkReady: false, updateOnSdkReadyFromCache: undefined, updateOnSdkTimedout: false })).toEqual({
294+
...INITIAL_STATUS,
295+
updateOnSdkReady: false,
296+
updateOnSdkReadyFromCache: false,
297+
updateOnSdkTimedout: false
298+
});
299+
return null;
300+
})}
301+
</SplitFactoryProvider>
302+
);
303+
});
304+
284305
});

src/__tests__/useSplitManager.test.tsx

+4-2
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,16 @@ describe('useSplitManager', () => {
3232
);
3333

3434
expect(hookResult).toStrictEqual({
35+
...INITIAL_STATUS,
3536
manager: outerFactory.manager(),
3637
client: outerFactory.client(),
3738
factory: outerFactory,
38-
...INITIAL_STATUS,
3939
});
4040

4141
act(() => (outerFactory.client() as any).__emitter__.emit(Event.SDK_READY));
4242

4343
expect(hookResult).toStrictEqual({
44+
...INITIAL_STATUS,
4445
manager: outerFactory.manager(),
4546
client: outerFactory.client(),
4647
factory: outerFactory,
@@ -80,16 +81,17 @@ describe('useSplitManager', () => {
8081
);
8182

8283
expect(hookResult).toStrictEqual({
84+
...INITIAL_STATUS,
8385
manager: outerFactory.manager(),
8486
client: outerFactory.client(),
8587
factory: outerFactory,
86-
...INITIAL_STATUS,
8788
});
8889

8990
act(() => (outerFactory.client() as any).__emitter__.emit(Event.SDK_READY));
9091
// act(() => (outerFactory.client() as any).__emitter__.emit(Event.SDK_READY));
9192

9293
expect(hookResult).toStrictEqual({
94+
...INITIAL_STATUS,
9395
manager: outerFactory.manager(),
9496
client: outerFactory.client(),
9597
factory: outerFactory,

src/__tests__/withSplitTreatments.test.tsx

+2-6
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ jest.mock('@splitsoftware/splitio/client', () => {
88
});
99
import { SplitFactory } from '@splitsoftware/splitio/client';
1010
import { sdkBrowser } from './testUtils/sdkConfigs';
11+
import { INITIAL_STATUS } from './testUtils/utils';
1112

1213
/** Test target */
1314
import { withSplitFactory } from '../withSplitFactory';
@@ -32,15 +33,10 @@ describe('withSplitTreatments', () => {
3233
expect((clientMock.getTreatmentsWithConfig as jest.Mock).mock.calls.length).toBe(0);
3334

3435
expect(props).toStrictEqual({
36+
...INITIAL_STATUS,
3537
factory: factory, client: clientMock,
3638
outerProp1: 'outerProp1', outerProp2: 2,
3739
treatments: getControlTreatmentsWithConfig(featureFlagNames),
38-
isReady: false,
39-
isReadyFromCache: false,
40-
hasTimedout: false,
41-
isTimedout: false,
42-
isDestroyed: false,
43-
lastUpdate: 0
4440
});
4541

4642
return null;

src/types.ts

+35-37
Original file line numberDiff line numberDiff line change
@@ -34,73 +34,71 @@ export interface ISplitStatus {
3434
isDestroyed: boolean;
3535

3636
/**
37-
* Indicates when was the last status event, either `SDK_READY`, `SDK_READY_FROM_CACHE`, `SDK_READY_TIMED_OUT` or `SDK_UPDATE`.
37+
* `lastUpdate` indicates the timestamp of the most recent status event. This timestamp is only updated for events that are being listened to,
38+
* configured via the `updateOnSdkReady` option for `SDK_READY` event, `updateOnSdkReadyFromCache` for `SDK_READY_FROM_CACHE` event,
39+
* `updateOnSdkTimedout` for `SDK_READY_TIMED_OUT` event, and `updateOnSdkUpdate` for `SDK_UPDATE` event.
3840
*/
3941
lastUpdate: number;
4042
}
4143

42-
/**
43-
* Split Context Value interface. It is used to define the value types of Split Context
44-
*/
45-
export interface ISplitContextValues extends ISplitStatus {
46-
47-
/**
48-
* Split factory instance.
49-
*
50-
* NOTE: This property is available for accessing factory methods not covered by the library hooks,
51-
* such as Logging configuration and User Consent.
52-
* @see {@link https://help.split.io/hc/en-us/articles/360020448791-JavaScript-SDK#logging}),
53-
* @see {@link https://help.split.io/hc/en-us/articles/360020448791-JavaScript-SDK#user-consent}
54-
*/
55-
factory?: SplitIO.IBrowserSDK;
56-
57-
/**
58-
* Split client instance.
59-
*
60-
* NOTE: This property is not recommended for direct use, as better alternatives are available:
61-
* - `useSplitTreatments` hook to evaluate feature flags.
62-
* - `useTrack` hook to track events.
63-
*
64-
* @see {@link https://help.split.io/hc/en-us/articles/360020448791-JavaScript-SDK#2-instantiate-the-sdk-and-create-a-new-split-client}
65-
*/
66-
client?: SplitIO.IBrowserClient;
67-
}
68-
6944
/**
7045
* Update Props interface. It defines the props used to configure what SDK events are listened to update the component.
7146
*/
7247
export interface IUpdateProps {
7348

7449
/**
75-
* `updateOnSdkUpdate` indicates if the component will update (i.e., re-render) in case of an `SDK_UPDATE` event.
76-
* If `true`, components consuming the context (such as `SplitClient` and `SplitTreatments`) will re-render on `SDK_UPDATE`.
50+
* `updateOnSdkUpdate` indicates if the hook or component will update (i.e., re-render) or not in case of an `SDK_UPDATE` event.
7751
* It's value is `true` by default.
7852
*/
7953
updateOnSdkUpdate?: boolean;
8054

8155
/**
82-
* `updateOnSdkTimedout` indicates if the component will update (i.e., re-render) in case of a `SDK_READY_TIMED_OUT` event.
83-
* If `true`, components consuming the context (such as `SplitClient` and `SplitTreatments`) will re-render on `SDK_READY_TIMED_OUT`.
56+
* `updateOnSdkTimedout` indicates if the hook or component will update (i.e., re-render) or not in case of a `SDK_READY_TIMED_OUT` event.
8457
* It's value is `true` by default.
8558
*/
8659
updateOnSdkTimedout?: boolean;
8760

8861
/**
89-
* `updateOnSdkReady` indicates if the component will update (i.e., re-render) in case of a `SDK_READY` event.
90-
* If `true`, components consuming the context (such as `SplitClient` and `SplitTreatments`) will re-render on `SDK_READY`.
62+
* `updateOnSdkReady` indicates if the hook or component will update (i.e., re-render) or not in case of a `SDK_READY` event.
9163
* It's value is `true` by default.
9264
*/
9365
updateOnSdkReady?: boolean;
9466

9567
/**
96-
* `updateOnSdkReadyFromCache` indicates if the component will update (i.e., re-render) in case of a `SDK_READY_FROM_CACHE` event.
97-
* If `true`, components consuming the context (such as `SplitClient` and `SplitTreatments`) will re-render on `SDK_READY_FROM_CACHE`.
68+
* `updateOnSdkReadyFromCache` indicates if the hook or component will update (i.e., re-render) or not in case of a `SDK_READY_FROM_CACHE` event.
9869
* This params is only relevant when using `'LOCALSTORAGE'` as storage type, since otherwise the event is never emitted.
9970
* It's value is `true` by default.
10071
*/
10172
updateOnSdkReadyFromCache?: boolean;
10273
}
10374

75+
/**
76+
* Split Context Value interface. It is used to define the value types of Split Context
77+
*/
78+
export interface ISplitContextValues extends ISplitStatus, IUpdateProps {
79+
80+
/**
81+
* Split factory instance.
82+
*
83+
* NOTE: This property is available for accessing factory methods not covered by the library hooks,
84+
* such as Logging configuration and User Consent.
85+
* @see {@link https://help.split.io/hc/en-us/articles/360020448791-JavaScript-SDK#logging}),
86+
* @see {@link https://help.split.io/hc/en-us/articles/360020448791-JavaScript-SDK#user-consent}
87+
*/
88+
factory?: SplitIO.IBrowserSDK;
89+
90+
/**
91+
* Split client instance.
92+
*
93+
* NOTE: This property is not recommended for direct use, as better alternatives are available:
94+
* - `useSplitTreatments` hook to evaluate feature flags.
95+
* - `useTrack` hook to track events.
96+
*
97+
* @see {@link https://help.split.io/hc/en-us/articles/360020448791-JavaScript-SDK#2-instantiate-the-sdk-and-create-a-new-split-client}
98+
*/
99+
client?: SplitIO.IBrowserClient;
100+
}
101+
104102
/**
105103
* Props interface for components wrapped by the `withSplitFactory` HOC. These props are provided by the HOC to the wrapped component.
106104
*
@@ -112,7 +110,7 @@ export interface ISplitFactoryChildProps extends ISplitContextValues { }
112110
* SplitFactoryProvider Props interface. These are the props accepted by the `SplitFactoryProvider` component,
113111
* used to instantiate a factory and provide it to the Split Context.
114112
*/
115-
export interface ISplitFactoryProviderProps {
113+
export interface ISplitFactoryProviderProps extends IUpdateProps {
116114

117115
/**
118116
* Config object used to instantiate a Split factory.

0 commit comments

Comments
 (0)