Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
2.6.0 (October 31, 2025)
2.6.0 (November 4, 2025)
- Added `useTreatment`, `useTreatments`, `useTreatmentWithConfig` and `useTreatmentsWithConfig` hooks to replace the now deprecated `useSplitTreatments` hook.
- Updated @splitsoftware/splitio package to version 11.8.0 that includes minor updates:
- Added new configuration for Fallback Treatments, which allows setting a treatment value and optional config to be returned in place of "control", either globally or by flag. Read more in our docs.
Expand Down
35 changes: 29 additions & 6 deletions MIGRATION-GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,32 @@

React SDK v2.0.0 has a few breaking changes that you should consider when migrating from a previous version. The main changes are:

### • Deprecated `useClient`, `useTreatments`, and `useManager` hooks have been removed.

Follow [this section](#migrating-to-get-react-sdk-v1100-improvements-replacing-the-deprecated-useclient-usetreatments-and-usemanager-hooks) to migrate to the new hooks `useSplitClient`, `useSplitTreatments`, and `useSplitManager`.
### • `useTreatments` hook was removed in v2.0.0, but re-introduced in v2.6.0 with a different API:

Since v2.6.0, there are 4 hooks variants to evaluate feature flags, to better cover the different evaluation methods available in the JavaScript SDK client:

- `useTreatment`: returns a treatment value for a given feature flag name. It calls `client.getTreatment()` method under the hood.
- `useTreatmentWithConfig`: returns a single treatment value and its configuration for a given feature flag name. It calls `client.getTreatmentWithConfig()` method under the hood.
- `useTreatments`: returns an object with treatment values for multiple feature flag names. It calls `client.getTreatments()` or `client.getTreatmentsByFlagSets()` methods under the hood, depending if the `names` or `flagSets` option is provided.
- `useTreatmentsWithConfig`: returns an object with treatment values and their configurations for multiple feature flag names. It calls `client.getTreatmentsWithConfig()` or `client.getTreatmentsWithConfigByFlagSets()` methods under the hood, depending if the `names` or `flagSets` option is provided.

The `useTreatments` hook from v1.x.x should be replaced with `useTreatmentsWithConfig`, as follows:

```javascript
// v1.x.x
const treatments = useTreatments(featureFlagNames, optionalAttributes, optionalSplitKey);

// v2.6.0+
const { treatments } = useTreatmentsWithConfig({ names: featureFlagNames, attributes: optionalAttributes, splitKey: optionalSplitKey });

// v2.0.0-v2.5.0
const { treatments } = useSplitTreatments({ names: featureFlagNames, attributes: optionalAttributes, splitKey: optionalSplitKey });
```

### • Deprecated `useClient` and `useManager` hooks have been removed.

Follow [this section](#migrating-to-get-react-sdk-v1100-improvements-replacing-the-deprecated-useclient-usetreatments-and-usemanager-hooks) to migrate to the new hooks `useSplitClient` and `useSplitManager`.

### • Updated the default value of `updateOnSdkUpdate` and `updateOnSdkTimedout` options to `true`.

Expand All @@ -15,7 +38,7 @@ Consider setting the `updateOnSdkUpdate` option to `false` to revert to the prev

The same applies for the equivalent props in the `[with]SplitClient` and `[with]SplitTreatments` components, although these components are deprecated and we recommend [migrating to their hook alternatives](#-high-order-components-withsplitclient-withsplittreatments-and-components-that-accept-a-render-function-as-child-component-splittreatments-and-splitclient-have-been-deprecated-and-might-be-removed-in-a-future-major-release).

### • Deprecated `SplitFactory` provider has been removed, `withSplitFactory` is deprecated, and `SplitFactoryProvider` doesn't accept `updateOn` props and a render function as children anymore.
### • Deprecated `SplitFactory` provider has been removed, `withSplitFactory` is deprecated, and `SplitFactoryProvider` doesn't accept a render function as children anymore.

To migrate your existing code to the new version of `SplitFactoryProvider`, consider the following refactor example:

Expand Down Expand Up @@ -53,21 +76,21 @@ should be refactored to:

```tsx
const MyComponent = () => {
const props: ISplitContextValues = useSplitClient({ updateOnSdkUpdate: false });
const props: ISplitContextValues = useSplitClient();
const { factory, client, isReady, isReadyFromCache, ... } = props;
...
};

const App = () => {
return (
<SplitFactoryProvider config={mySplitConfig} attributes={DEFAULT_CLIENT_ATTRIBUTES} >
<SplitFactoryProvider config={mySplitConfig} updateOnSdkUpdate={false} attributes={DEFAULT_CLIENT_ATTRIBUTES} >
<MyComponent />
</SplitFactoryProvider>
);
};
```

Notice that `MyComponent` was refactored to use the `useSplitClient` hook and is passed as a React JSX element rather than a render function. The `useSplitClient` hook is called without providing a `splitKey` param. This means that the default client (whose key is set in the `core.key` property of the `mySplitConfig` object) will be used, and the `updateOnSdkUpdate` and `attributes` props are passed as options to the hook.
Notice that `MyComponent` was refactored to use the `useSplitClient` hook and is passed as a React JSX element rather than a render function. The `useSplitClient` hook is called without providing a `splitKey` param. This means that the default client (whose key is set in the `core.key` property of the `mySplitConfig` object) will be used.

### • High-Order-Components (`withSplitClient`, `withSplitTreatments`) and components that accept a render function as child component (`SplitTreatments`, and `SplitClient`) have been deprecated and might be removed in a future major release.

Expand Down
46 changes: 23 additions & 23 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@splitsoftware/splitio-react",
"version": "2.5.0",
"version": "2.6.0",
"description": "A React library to easily integrate and use Split JS SDK",
"main": "cjs/index.js",
"module": "esm/index.js",
Expand Down Expand Up @@ -63,7 +63,7 @@
},
"homepage": "https://github.com/splitio/react-client#readme",
"dependencies": {
"@splitsoftware/splitio": "11.7.2-rc.4",
"@splitsoftware/splitio": "11.8.0",
"memoize-one": "^5.1.1",
"shallowequal": "^1.1.0",
"tslib": "^2.3.1"
Expand Down
8 changes: 8 additions & 0 deletions src/__tests__/testUtils/sdkConfigs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,11 @@ export const sdkBrowser: SplitIO.IBrowserSettings = {
key: 'customer-key',
},
};

export const sdkBrowserWithConfig: SplitIO.IBrowserSettings = {
...sdkBrowser,
fallbackTreatments: {
global: 'control_global',
byFlag: { ff1: { treatment: 'control_ff1', config: 'control_ff1_config' } }
}
};
16 changes: 14 additions & 2 deletions src/__tests__/useTreatment.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ jest.mock('@splitsoftware/splitio/client', () => {
return { SplitFactory: mockSdk() };
});
import { SplitFactory } from '@splitsoftware/splitio/client';
import { sdkBrowser } from './testUtils/sdkConfigs';
import { sdkBrowser, sdkBrowserWithConfig } from './testUtils/sdkConfigs';
import { CONTROL, EXCEPTION_NO_SFP } from '../constants';

/** Test target */
Expand Down Expand Up @@ -96,7 +96,7 @@ describe('useTreatment', () => {
}).toThrow(EXCEPTION_NO_SFP);
});

test('useTreatment must update on SDK events', async () => {
test('must update on SDK events', async () => {
const outerFactory = SplitFactory(sdkBrowser);
const mainClient = outerFactory.client() as any;
const user2Client = outerFactory.client('user_2') as any;
Expand Down Expand Up @@ -171,4 +171,16 @@ describe('useTreatment', () => {
expect(user2Client.getTreatment).toHaveBeenLastCalledWith('split_test', undefined, undefined);
});

test('returns fallback treatment if the client is not operational', () => {
render(
<SplitFactoryProvider config={sdkBrowserWithConfig} >
{React.createElement(() => {
expect(useTreatment({ name: featureFlagName, attributes, properties }).treatment).toEqual('control_global');
expect(useTreatment({ name: 'ff1', attributes, properties }).treatment).toEqual('control_ff1');
return null;
})}
</SplitFactoryProvider>
);
});

});
16 changes: 14 additions & 2 deletions src/__tests__/useTreatmentWithConfig.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ jest.mock('@splitsoftware/splitio/client', () => {
return { SplitFactory: mockSdk() };
});
import { SplitFactory } from '@splitsoftware/splitio/client';
import { sdkBrowser } from './testUtils/sdkConfigs';
import { sdkBrowser, sdkBrowserWithConfig } from './testUtils/sdkConfigs';
import { CONTROL_WITH_CONFIG, EXCEPTION_NO_SFP } from '../constants';

/** Test target */
Expand Down Expand Up @@ -96,7 +96,7 @@ describe('useTreatmentWithConfig', () => {
}).toThrow(EXCEPTION_NO_SFP);
});

test('useTreatmentWithConfig must update on SDK events', async () => {
test('must update on SDK events', async () => {
const outerFactory = SplitFactory(sdkBrowser);
const mainClient = outerFactory.client() as any;
const user2Client = outerFactory.client('user_2') as any;
Expand Down Expand Up @@ -171,4 +171,16 @@ describe('useTreatmentWithConfig', () => {
expect(user2Client.getTreatmentWithConfig).toHaveBeenLastCalledWith('split_test', undefined, undefined);
});

test('returns fallback treatment if the client is not operational', () => {
render(
<SplitFactoryProvider config={sdkBrowserWithConfig} >
{React.createElement(() => {
expect(useTreatmentWithConfig({ name: featureFlagName, attributes, properties }).treatment).toEqual({ treatment: 'control_global', config: null });
expect(useTreatmentWithConfig({ name: 'ff1', attributes, properties }).treatment).toEqual({ treatment: 'control_ff1', config: 'control_ff1_config' });
return null;
})}
</SplitFactoryProvider>
);
});

});
16 changes: 14 additions & 2 deletions src/__tests__/useTreatments.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ jest.mock('@splitsoftware/splitio/client', () => {
return { SplitFactory: mockSdk() };
});
import { SplitFactory } from '@splitsoftware/splitio/client';
import { sdkBrowser } from './testUtils/sdkConfigs';
import { sdkBrowser, sdkBrowserWithConfig } from './testUtils/sdkConfigs';
import { CONTROL, EXCEPTION_NO_SFP } from '../constants';

/** Test target */
Expand Down Expand Up @@ -132,7 +132,7 @@ describe('useTreatments', () => {
);
});

test('useTreatments must update on SDK events', async () => {
test('must update on SDK events', async () => {
const outerFactory = SplitFactory(sdkBrowser);
const mainClient = outerFactory.client() as any;
const user2Client = outerFactory.client('user_2') as any;
Expand Down Expand Up @@ -226,4 +226,16 @@ describe('useTreatments', () => {
);
});

test('returns fallback treatments if the client is not operational', () => {
render(
<SplitFactoryProvider config={sdkBrowserWithConfig} >
{React.createElement(() => {
const { treatments } = useTreatments({ names: ['ff1', 'ff2'], attributes, properties });
expect(treatments).toEqual({ ff1: 'control_ff1', ff2: 'control_global' });
return null;
})}
</SplitFactoryProvider>
);
});

});
16 changes: 14 additions & 2 deletions src/__tests__/useTreatmentsWithConfig.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ jest.mock('@splitsoftware/splitio/client', () => {
return { SplitFactory: mockSdk() };
});
import { SplitFactory } from '@splitsoftware/splitio/client';
import { sdkBrowser } from './testUtils/sdkConfigs';
import { sdkBrowser, sdkBrowserWithConfig } from './testUtils/sdkConfigs';
import { CONTROL_WITH_CONFIG, EXCEPTION_NO_SFP } from '../constants';

/** Test target */
Expand Down Expand Up @@ -132,7 +132,7 @@ describe('useTreatmentsWithConfig', () => {
);
});

test('useTreatmentsWithConfig must update on SDK events', async () => {
test('must update on SDK events', async () => {
const outerFactory = SplitFactory(sdkBrowser);
const mainClient = outerFactory.client() as any;
const user2Client = outerFactory.client('user_2') as any;
Expand Down Expand Up @@ -232,4 +232,16 @@ describe('useTreatmentsWithConfig', () => {
);
});

test('returns fallback treatments if the client is not operational', () => {
render(
<SplitFactoryProvider config={sdkBrowserWithConfig} >
{React.createElement(() => {
const { treatments } = useTreatmentsWithConfig({ names: ['ff1', 'ff2'], attributes, properties });
expect(treatments).toEqual({ ff1: { treatment: 'control_ff1', config: 'control_ff1_config' }, ff2: { treatment: 'control_global', config: null } });
return null;
})}
</SplitFactoryProvider>
);
});

});
4 changes: 2 additions & 2 deletions src/useTreatment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ import { useSplitClient } from './useSplitClient';
*/
export function useTreatment(options: IUseTreatmentOptions): IUseTreatmentResult {
const context = useSplitClient({ ...options, attributes: undefined });
const { client, lastUpdate } = context;
const { factory, client, lastUpdate } = context;
const { name, attributes, properties } = options;

const getTreatment = React.useMemo(memoizeGetTreatment, []);

// Shallow copy `client.getAttributes` result for memoization, as it returns the same reference unless `client.clearAttributes` is invoked.
// Note: the same issue occurs with the `names` and `attributes` arguments if they are mutated directly by the user instead of providing a new object.
const treatment = getTreatment(client, lastUpdate, [name], attributes, client ? { ...client.getAttributes() } : {}, undefined, properties && { properties });
const treatment = getTreatment(client, lastUpdate, [name], attributes, client ? { ...client.getAttributes() } : {}, undefined, properties && { properties }, factory);

return {
...context,
Expand Down
4 changes: 2 additions & 2 deletions src/useTreatmentWithConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ import { useSplitClient } from './useSplitClient';
*/
export function useTreatmentWithConfig(options: IUseTreatmentOptions): IUseTreatmentWithConfigResult {
const context = useSplitClient({ ...options, attributes: undefined });
const { client, lastUpdate } = context;
const { factory, client, lastUpdate } = context;
const { name, attributes, properties } = options;

const getTreatmentWithConfig = React.useMemo(memoizeGetTreatmentWithConfig, []);

// Shallow copy `client.getAttributes` result for memoization, as it returns the same reference unless `client.clearAttributes` is invoked.
// Note: the same issue occurs with the `names` and `attributes` arguments if they are mutated directly by the user instead of providing a new object.
const treatment = getTreatmentWithConfig(client, lastUpdate, [name], attributes, client ? { ...client.getAttributes() } : {}, undefined, properties && { properties });
const treatment = getTreatmentWithConfig(client, lastUpdate, [name], attributes, client ? { ...client.getAttributes() } : {}, undefined, properties && { properties }, factory);

return {
...context,
Expand Down
Loading
Loading