Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
1 change: 1 addition & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
2.6.0 (October 31, 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.
Expand Down
21 changes: 14 additions & 7 deletions src/__tests__/utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
import { CONTROL_WITH_CONFIG } from '../constants';
import { getControlTreatmentsWithConfig } from '../utils';
import { CONTROL, CONTROL_WITH_CONFIG } from '../constants';
import { getControlTreatments } from '../utils';

describe('getControlTreatmentsWithConfig', () => {
describe('getControlTreatments', () => {

it('should return an empty object if an empty array is provided', () => {
expect(Object.values(getControlTreatmentsWithConfig([])).length).toBe(0);
expect(Object.values(getControlTreatments([], true)).length).toBe(0);
expect(Object.values(getControlTreatments([], false)).length).toBe(0);
});

it('should return an empty object if an empty array is provided', () => {
it('should return an object with control treatments if an array of feature flag names is provided', () => {
const featureFlagNames = ['split1', 'split2'];
const treatments: SplitIO.TreatmentsWithConfig = getControlTreatmentsWithConfig(featureFlagNames);
const treatmentsWithConfig: SplitIO.TreatmentsWithConfig = getControlTreatments(featureFlagNames, true);
featureFlagNames.forEach((featureFlagName) => {
expect(treatmentsWithConfig[featureFlagName]).toBe(CONTROL_WITH_CONFIG);
});
expect(Object.keys(treatmentsWithConfig).length).toBe(featureFlagNames.length);

const treatments: SplitIO.Treatments = getControlTreatments(featureFlagNames, false);
featureFlagNames.forEach((featureFlagName) => {
expect(treatments[featureFlagName]).toBe(CONTROL_WITH_CONFIG);
expect(treatments[featureFlagName]).toBe(CONTROL);
});
expect(Object.keys(treatments).length).toBe(featureFlagNames.length);
});
Expand Down
6 changes: 3 additions & 3 deletions src/__tests__/withSplitTreatments.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ import { INITIAL_STATUS } from './testUtils/utils';
import { withSplitFactory } from '../withSplitFactory';
import { withSplitClient } from '../withSplitClient';
import { withSplitTreatments } from '../withSplitTreatments';
import { getControlTreatmentsWithConfig } from '../utils';
import { getControlTreatments } from '../utils';

const featureFlagNames = ['split1', 'split2'];

describe('withSplitTreatments', () => {

it(`passes Split props and outer props to the child.
In this test, the value of "props.treatments" is obtained by the function "getControlTreatmentsWithConfig",
In this test, the value of "props.treatments" is obtained by the function "getControlTreatments",
and not "client.getTreatmentsWithConfig" since the client is not ready.`, () => {

const Component = withSplitFactory(sdkBrowser)<{ outerProp1: string, outerProp2: number }>(
Expand All @@ -36,7 +36,7 @@ describe('withSplitTreatments', () => {
...INITIAL_STATUS,
factory: factory, client: clientMock,
outerProp1: 'outerProp1', outerProp2: 2,
treatments: getControlTreatmentsWithConfig(featureFlagNames),
treatments: getControlTreatments(featureFlagNames, true),
});

return null;
Expand Down
12 changes: 11 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ export { SplitFactoryProvider } from './SplitFactoryProvider';

// Hooks
export { useTrack } from './useTrack';
export { useTreatment } from './useTreatment';
export { useTreatments } from './useTreatments';
export { useTreatmentWithConfig } from './useTreatmentWithConfig';
export { useTreatmentsWithConfig } from './useTreatmentsWithConfig';
export { useSplitClient } from './useSplitClient';
export { useSplitTreatments } from './useSplitTreatments';
export { useSplitManager } from './useSplitManager';
Expand All @@ -34,5 +38,11 @@ export type {
IUpdateProps,
IUseSplitClientOptions,
IUseSplitTreatmentsOptions,
IUseSplitManagerResult
IUseSplitManagerResult,
IUseTreatmentOptions,
IUseTreatmentsOptions,
IUseTreatmentResult,
IUseTreatmentWithConfigResult,
IUseTreatmentsResult,
IUseTreatmentsWithConfigResult
} from './types';
119 changes: 108 additions & 11 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@ export interface ISplitClientProps extends IUseSplitClientOptions {
children: ((props: ISplitClientChildProps) => ReactNode) | ReactNode;
}

/**
* Result of the `useSplitManager` hook.
*/
export interface IUseSplitManagerResult extends ISplitContextValues {
/**
* Split manager instance.
Expand All @@ -144,6 +147,17 @@ export interface IUseSplitManagerResult extends ISplitContextValues {
manager?: SplitIO.IManager;
}

type EvaluationOptions = SplitIO.EvaluationOptions & {

/**
* An object of type Attributes used to evaluate the feature flags.
*/
attributes?: SplitIO.Attributes;
}

/**
* @deprecated `useSplitTreatments` will be removed in a future major release. We recommend replacing it with the `useTreatment*` hooks.
*/
export type GetTreatmentsOptions = ({

/**
Expand All @@ -158,27 +172,51 @@ export type GetTreatmentsOptions = ({
*/
flagSets: string[];
names?: undefined;
}) & {
}) & EvaluationOptions;

/**
* An object of type Attributes used to evaluate the feature flags.
*/
attributes?: SplitIO.Attributes;
/**
* Options object accepted by the `useSplitTreatments` hook, used to call `client.getTreatmentsWithConfig()`, or `client.getTreatmentsWithConfigByFlagSets()`,
* depending on whether `names` or `flagSets` options are provided, and to retrieve the result along with the Split context.
*
* @deprecated `useSplitTreatments` will be removed in a future major release. We recommend replacing it with the `useTreatment*` hooks.
*/
export type IUseSplitTreatmentsOptions = GetTreatmentsOptions & IUseSplitClientOptions;

/**
* Options object accepted by the `useTreatment` and `useTreatmentWithConfig` hooks.
*/
export type IUseTreatmentOptions = {

/**
* Optional properties to append to the generated impression object sent to Split backend.
* Feature flag name to evaluate.
*/
properties?: SplitIO.Properties;
}
name: string;
} & EvaluationOptions & IUseSplitClientOptions;


/**
* Options object accepted by the `useSplitTreatments` hook, used to call `client.getTreatmentsWithConfig()`, or `client.getTreatmentsWithConfigByFlagSets()`,
* depending on whether `names` or `flagSets` options are provided, and to retrieve the result along with the Split context.
* Options object accepted by the `useTreatments` and `useTreatmentsWithConfig` hooks.
*/
export type IUseSplitTreatmentsOptions = GetTreatmentsOptions & IUseSplitClientOptions;
export type IUseTreatmentsOptions = ({

/**
* List of feature flag names to evaluate. Either this or the `flagSets` property must be provided. If both are provided, the `flagSets` option is ignored.
*/
names: string[];
flagSets?: undefined;
} | {

/**
* List of feature flag sets to evaluate. Either this or the `names` property must be provided. If both are provided, the `flagSets` option is ignored.
*/
flagSets: string[];
names?: undefined;
}) & EvaluationOptions & IUseSplitClientOptions;

/**
* SplitTreatments Child Props interface. These are the props that the child component receives from the 'SplitTreatments' component.
*
* @deprecated `SplitTreatments` will be removed in a future major release. We recommend replacing it with the `useTreatments*` hooks.
*/
export interface ISplitTreatmentsChildProps extends ISplitContextValues {

Expand All @@ -196,9 +234,68 @@ export interface ISplitTreatmentsChildProps extends ISplitContextValues {
treatments: SplitIO.TreatmentsWithConfig;
}

/**
* Result of the `useTreatment` hook.
*/
export interface IUseTreatmentResult extends ISplitContextValues {
/**
* The treatment string for a feature flag, returned by client.getTreatment().
*/
treatment: SplitIO.Treatment;
}

/**
* Result of the `useTreatmentWithConfig` hook.
*/
export interface IUseTreatmentWithConfigResult extends ISplitContextValues {
/**
* The treatment with config for a feature flag, returned by client.getTreatmentWithConfig().
*/
treatment: SplitIO.TreatmentWithConfig;
}

/**
* Result of the `useTreatments` hook.
*/
export interface IUseTreatmentsResult extends ISplitContextValues {
/**
* An object with the treatment strings for a bulk of feature flags, returned by client.getTreatments() or client.getTreatmentsByFlagSets().
* For example:
*
* ```js
* {
* feature1: 'on',
* feature2: 'off'
* }
* ```
*/
treatments: SplitIO.Treatments;
}

/**
* Result of the `useTreatmentsWithConfig` hook.
*/
export interface IUseTreatmentsWithConfigResult extends ISplitContextValues {

/**
* An object with the treatments with configs for a bulk of feature flags, returned by client.getTreatmentsWithConfig() or client.getTreatmentsWithConfigByFlagSets().
* Each existing configuration is a stringified version of the JSON you defined on the Split user interface. For example:
*
* ```js
* {
* feature1: { treatment: 'on', config: null },
* feature2: { treatment: 'off', config: '{"bannerText":"Click here."}' }
* }
* ```
*/
treatments: SplitIO.TreatmentsWithConfig;
}

/**
* SplitTreatments Props interface. These are the props accepted by SplitTreatments component, used to call 'client.getTreatmentsWithConfig()', or 'client.getTreatmentsWithConfigByFlagSets()',
* depending on whether `names` or `flagSets` props are provided, and to pass the result to the child component.
*
* @deprecated `SplitTreatments` will be removed in a future major release. We recommend replacing it with the `useTreatments*` hooks.
*/
export type ISplitTreatmentsProps = IUseSplitTreatmentsOptions & {

Expand Down
21 changes: 4 additions & 17 deletions src/useSplitTreatments.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import * as React from 'react';
import { memoizeGetTreatmentsWithConfig } from './utils';
import { ISplitTreatmentsChildProps, IUseSplitTreatmentsOptions } from './types';
import { useSplitClient } from './useSplitClient';
import { useTreatmentsWithConfig } from '.';

/**
* `useSplitTreatments` is a hook that returns an Split Context object extended with a `treatments` property object that contains feature flag evaluations.
Expand All @@ -17,20 +15,9 @@ import { useSplitClient } from './useSplitClient';
* ```
*
* @see {@link https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/client-side-sdks/javascript-sdk/#get-treatments-with-configurations}
*
* @deprecated `useSplitTreatments` will be removed in a future major release. We recommend replacing it with the `useTreatment*` hooks.
*/
export function useSplitTreatments(options: IUseSplitTreatmentsOptions): ISplitTreatmentsChildProps {
const context = useSplitClient({ ...options, attributes: undefined });
const { client, lastUpdate } = context;
const { names, flagSets, attributes, properties } = options;

const getTreatmentsWithConfig = React.useMemo(memoizeGetTreatmentsWithConfig, []);

// 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 treatments = getTreatmentsWithConfig(client, lastUpdate, names, attributes, client ? { ...client.getAttributes() } : {}, flagSets, properties && { properties });

return {
...context,
treatments,
};
return useTreatmentsWithConfig(options);
}
35 changes: 35 additions & 0 deletions src/useTreatment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import * as React from 'react';
import { memoizeGetTreatment } from './utils';
import { IUseTreatmentResult, IUseTreatmentOptions } from './types';
import { useSplitClient } from './useSplitClient';

/**
* `useTreatment` is a hook that returns an Split Context object extended with a `treatment` property.
* It uses the `useSplitClient` hook to access the client, and invokes the `client.getTreatment()` method.
*
* @param options - An options object with a feature flag name to evaluate, and an optional `attributes` and `splitKey` values to configure the client.
* @returns A Split Context object extended with a Treatment instance, that might be a control treatment if the client is not available or ready, or if the provided feature flag name does not exist.
*
* @example
* ```js
* const { treatment, isReady, isReadyFromCache, hasTimedout, lastUpdate, ... } = useTreatment({ name: 'feature_1'});
* ```
*
* @see {@link https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/client-side-sdks/javascript-sdk/#multiple-evaluations-at-once}
*/
export function useTreatment(options: IUseTreatmentOptions): IUseTreatmentResult {
const context = useSplitClient({ ...options, attributes: undefined });
const { 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 });

return {
...context,
treatment,
};
}
35 changes: 35 additions & 0 deletions src/useTreatmentWithConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import * as React from 'react';
import { memoizeGetTreatmentWithConfig } from './utils';
import { IUseTreatmentWithConfigResult, IUseTreatmentOptions } from './types';
import { useSplitClient } from './useSplitClient';

/**
* `useTreatmentWithConfig` is a hook that returns an Split Context object extended with a `treatment` property.
* It uses the `useSplitClient` hook to access the client, and invokes the `client.getTreatmentWithConfig()` method.
*
* @param options - An options object with a feature flag name to evaluate, and an optional `attributes` and `splitKey` values to configure the client.
* @returns A Split Context object extended with a TreatmentWithConfig instance, that might be a control treatment if the client is not available or ready, or if the provided feature flag name does not exist.
*
* @example
* ```js
* const { treatment: { treatment, config }, isReady, isReadyFromCache, hasTimedout, lastUpdate, ... } = useTreatmentWithConfig({ name: 'feature_1'});
* ```
*
* @see {@link https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/client-side-sdks/javascript-sdk/#get-treatments-with-configurations}
*/
export function useTreatmentWithConfig(options: IUseTreatmentOptions): IUseTreatmentWithConfigResult {
const context = useSplitClient({ ...options, attributes: undefined });
const { 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 });

return {
...context,
treatment,
};
}
36 changes: 36 additions & 0 deletions src/useTreatments.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import * as React from 'react';
import { memoizeGetTreatments } from './utils';
import { IUseTreatmentsResult, IUseTreatmentsOptions } from './types';
import { useSplitClient } from './useSplitClient';

/**
* `useTreatments` is a hook that returns an Split Context object extended with a `treatments` property object that contains feature flag evaluations.
* It uses the `useSplitClient` hook to access the client, and invokes the `client.getTreatments()` method if the `names` option is provided,
* or the `client.getTreatmentsByFlagSets()` method if the `flagSets` option is provided.
*
* @param options - An options object with a list of feature flag names or flag sets to evaluate, and an optional `attributes` and `splitKey` values to configure the client.
* @returns A Split Context object extended with a Treatments instance, that might contain control treatments if the client is not available or ready, or if feature flag names do not exist.
*
* @example
* ```js
* const { treatments: { feature_1, feature_2 }, isReady, isReadyFromCache, hasTimedout, lastUpdate, ... } = useTreatments({ names: ['feature_1', 'feature_2']});
* ```
*
* @see {@link https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/client-side-sdks/javascript-sdk/#multiple-evaluations-at-once}
*/
export function useTreatments(options: IUseTreatmentsOptions): IUseTreatmentsResult {
const context = useSplitClient({ ...options, attributes: undefined });
const { client, lastUpdate } = context;
const { names, flagSets, attributes, properties } = options;

const getTreatments = React.useMemo(memoizeGetTreatments, []);

// 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 treatments = getTreatments(client, lastUpdate, names, attributes, client ? { ...client.getAttributes() } : {}, flagSets, properties && { properties });

return {
...context,
treatments,
};
}
Loading
Loading