Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
394a55d
fix: use Split SDK logger instead of console.log for warnings and errors
EmilianoSanchez Oct 21, 2025
aa039d6
refactor: simplify input validation for the case where the SDK is not…
EmilianoSanchez Oct 27, 2025
f75708f
Added `useTreatment`, `useTreatments`, `useTreatmentWithConfig` and `…
EmilianoSanchez Oct 27, 2025
b35dc78
Add tests
EmilianoSanchez Oct 27, 2025
709a35b
Update comments and metadata
EmilianoSanchez Oct 27, 2025
6c44714
chore: update JS SDK dependency and adapt to new getStatus API
EmilianoSanchez Oct 29, 2025
5b1229c
Update changelog entry
EmilianoSanchez Oct 29, 2025
3ced1c1
Merge pull request #248 from splitio/use-sdk-logger
EmilianoSanchez Oct 29, 2025
c90509f
Merge branch 'development' into FME-10595-add-evaluation-hooks
EmilianoSanchez Oct 29, 2025
2251ad5
Merge branch 'FME-10595-add-evaluation-hooks' into FME-10595-add-eval…
EmilianoSanchez Oct 29, 2025
284d1ee
feat: add support for fallback treatments when client is not operational
EmilianoSanchez Oct 30, 2025
9a58781
docs: update migration guide with reintroduced useTreatments hook det…
EmilianoSanchez Oct 31, 2025
54aaea6
Unit tests
EmilianoSanchez Nov 4, 2025
571bec2
Merge pull request #250 from splitio/FME-10595-add-evaluation-hooks-t…
EmilianoSanchez Nov 4, 2025
596cf92
Merge pull request #251 from splitio/migration-guide-update
EmilianoSanchez Nov 4, 2025
94dbb33
Merge pull request #252 from splitio/fallback-treatment-support
EmilianoSanchez Nov 4, 2025
202939c
refactor: reorganize utils.ts with better code structure and document…
EmilianoSanchez Nov 4, 2025
93ac080
Merge pull request #249 from splitio/FME-10595-add-evaluation-hooks
EmilianoSanchez Nov 4, 2025
9144ded
refactor: move treatment evaluation logic from utils to hook modules …
EmilianoSanchez Nov 4, 2025
6fa03ad
Merge pull request #254 from splitio/refactor-for-fallback-treatment
EmilianoSanchez Nov 4, 2025
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
7 changes: 7 additions & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
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.
- Added support for custom loggers: added `logger` configuration option and `factory.Logger.setLogger` method to allow the SDK to use a custom logger.
- Updated the SDK_READY_FROM_CACHE event to be emitted alongside the SDK_READY event if it hasn’t already been emitted.

2.5.0 (September 18, 2025)
- Updated @splitsoftware/splitio package to version 11.6.0 that includes minor updates:
- Added `storage.wrapper` configuration option to allow the SDK to use a custom storage wrapper for the storage type `LOCALSTORAGE`. Default value is `window.localStorage`.
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 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
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Below is a simple example that describes the instantiation and most basic usage
import React from 'react';

// Import SDK functions
import { SplitFactoryProvider, useSplitTreatments } from '@splitsoftware/splitio-react';
import { SplitFactoryProvider, useTreatment } from '@splitsoftware/splitio-react';

// Define your config object
const CONFIG = {
Expand All @@ -29,18 +29,18 @@ const CONFIG = {
};

function MyComponent() {
// Evaluate feature flags with useSplitTreatments hook
const { treatments: { FEATURE_FLAG_NAME }, isReady } = useSplitTreatments({ names: ['FEATURE_FLAG_NAME'] });
// Evaluate a feature flag with useTreatment hook
const { treatment, isReady } = useTreatment({ name: 'FEATURE_FLAG_NAME' });

// Check SDK readiness using isReady prop
if (!isReady) return <div>Loading SDK ...</div>;

if (FEATURE_FLAG_NAME.treatment === 'on') {
// return JSX for on treatment
} else if (FEATURE_FLAG_NAME.treatment === 'off') {
// return JSX for off treatment
if (treatment === 'on') {
// return JSX for 'on' treatment
} else if (treatment === 'off') {
// return JSX for 'off' treatment
} else {
// return JSX for control treatment
// return JSX for 'control' treatment
};
}

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.6.0",
"@splitsoftware/splitio": "11.8.0",
"memoize-one": "^5.1.1",
"shallowequal": "^1.1.0",
"tslib": "^2.3.1"
Expand Down
2 changes: 0 additions & 2 deletions src/SplitClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ import { useSplitClient } from './useSplitClient';
*
* The underlying SDK client can be changed during the component lifecycle
* if the component is updated with a different splitKey prop.
*
* @deprecated `SplitClient` will be removed in a future major release. We recommend replacing it with the `useSplitClient` hook.
*/
export function SplitClient(props: ISplitClientProps) {
const { children } = props;
Expand Down
2 changes: 1 addition & 1 deletion src/SplitFactoryProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export function SplitFactoryProvider(props: ISplitFactoryProviderProps) {
// Effect to initialize and destroy the factory when config is provided
React.useEffect(() => {
if (propFactory) {
if (config) console.log(WARN_SF_CONFIG_AND_FACTORY);
if (config) (propFactory.settings as any).log.warn(WARN_SF_CONFIG_AND_FACTORY);
return;
}

Expand Down
2 changes: 1 addition & 1 deletion src/SplitTreatments.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { useSplitTreatments } from './useSplitTreatments';
* call the 'client.getTreatmentsWithConfig()' method if the `names` prop is provided, or the 'client.getTreatmentsWithConfigByFlagSets()' method
* if the `flagSets` prop is provided. It then passes the resulting treatments to a child component as a function.
*
* @deprecated `SplitTreatments` will be removed in a future major release. We recommend replacing it with the `useSplitTreatments` hook.
* @deprecated `SplitTreatments` will be removed in a future major release. We recommend replacing it with the `useTreatment*` hooks.
*/
export function SplitTreatments(props: ISplitTreatmentsProps) {
const { children } = props;
Expand Down
19 changes: 10 additions & 9 deletions src/__tests__/SplitClient.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ describe('SplitClient', () => {
client: outerFactory.client(),
isReady: true,
isReadyFromCache: true,
isOperational: true,
lastUpdate: getStatus(outerFactory.client()).lastUpdate
});

Expand Down Expand Up @@ -141,7 +142,7 @@ describe('SplitClient', () => {
expect(statusProps).toStrictEqual([false, false, true, true]);
break;
case 2: // Updated. Although `updateOnSdkReady` is false, status props must reflect the current status of the client.
expect(statusProps).toStrictEqual([true, false, true, false]);
expect(statusProps).toStrictEqual([true, true, true, false]);
break;
default:
fail('Child must not be rerendered');
Expand Down Expand Up @@ -182,7 +183,7 @@ describe('SplitClient', () => {
expect(statusProps).toStrictEqual([false, false, false, false]);
break;
case 1: // Ready
expect(statusProps).toStrictEqual([true, false, true, false]); // not rerendering on SDK_TIMEOUT, but hasTimedout reflects the current state
expect(statusProps).toStrictEqual([true, true, true, false]); // not rerendering on SDK_TIMEOUT, but hasTimedout reflects the current state
break;
default:
fail('Child must not be rerendered');
Expand Down Expand Up @@ -214,7 +215,7 @@ describe('SplitClient', () => {
count++;

// side effect in the render phase
if (!(client as any).__getStatus().isReady) {
if (!client!.getStatus().isReady) {
(client as any).__emitter__.emit(Event.SDK_READY);
}

Expand Down Expand Up @@ -318,11 +319,11 @@ describe('SplitClient', () => {
break;
case 4:
expect(client).toBe(outerFactory.client('user3'));
expect(statusProps).toStrictEqual([true, false, false, false]);
expect(statusProps).toStrictEqual([true, true, false, false]);
break;
case 5:
expect(client).toBe(outerFactory.client('user3'));
expect(statusProps).toStrictEqual([true, false, false, false]);
expect(statusProps).toStrictEqual([true, true, false, false]);
break;
default:
fail('Child must not be rerendered');
Expand Down Expand Up @@ -501,7 +502,7 @@ describe('SplitFactoryProvider + SplitClient', () => {
expect(statusProps).toStrictEqual([false, false, true, true]);
break;
case 2: // Updated. Although `updateOnSdkReady` is false, status props must reflect the current status of the client.
expect(statusProps).toStrictEqual([true, false, true, false]);
expect(statusProps).toStrictEqual([true, true, true, false]);
break;
default:
fail('Child must not be rerendered');
Expand Down Expand Up @@ -542,7 +543,7 @@ describe('SplitFactoryProvider + SplitClient', () => {
expect(statusProps).toStrictEqual([false, false, true, true]);
break;
case 2: // Updated. Although `updateOnSdkReady` is false, status props must reflect the current status of the client.
expect(statusProps).toStrictEqual([true, false, true, false]);
expect(statusProps).toStrictEqual([true, true, true, false]);
break;
default:
fail('Child must not be rerendered');
Expand Down Expand Up @@ -578,7 +579,7 @@ describe('SplitFactoryProvider + SplitClient', () => {
expect(statusProps).toStrictEqual([false, false, false, false]);
break;
case 1: // Ready
expect(statusProps).toStrictEqual([true, false, true, false]); // not rerendering on SDK_TIMEOUT, but hasTimedout reflects the current state
expect(statusProps).toStrictEqual([true, true, true, false]); // not rerendering on SDK_TIMEOUT, but hasTimedout reflects the current state
break;
default:
fail('Child must not be rerendered');
Expand Down Expand Up @@ -615,7 +616,7 @@ describe('SplitFactoryProvider + SplitClient', () => {
expect(statusProps).toStrictEqual([false, false, false, false]);
break;
case 1: // Ready
expect(statusProps).toStrictEqual([true, false, true, false]); // not rerendering on SDK_TIMEOUT, but hasTimedout reflects the current state
expect(statusProps).toStrictEqual([true, true, true, false]); // not rerendering on SDK_TIMEOUT, but hasTimedout reflects the current state
break;
default:
fail('Child must not be rerendered');
Expand Down
3 changes: 2 additions & 1 deletion src/__tests__/SplitFactoryProvider.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ describe('SplitFactoryProvider', () => {
client: outerFactory.client(),
isReady: true,
isReadyFromCache: true,
isOperational: true,
lastUpdate: getStatus(outerFactory.client()).lastUpdate
});
return null;
Expand Down Expand Up @@ -113,7 +114,7 @@ describe('SplitFactoryProvider', () => {
</SplitFactoryProvider>
);

expect(logSpy).toBeCalledWith(WARN_SF_CONFIG_AND_FACTORY);
expect(logSpy).toBeCalledWith('[WARN] splitio => ' + WARN_SF_CONFIG_AND_FACTORY);
logSpy.mockRestore();
});

Expand Down
Loading