Skip to content

Commit

Permalink
Merge branch 'hooks_baseline' into flagSets
Browse files Browse the repository at this point in the history
  • Loading branch information
EmilianoSanchez committed Nov 14, 2023
2 parents 3f24e24 + 3e726f9 commit 20091ca
Show file tree
Hide file tree
Showing 33 changed files with 85 additions and 510 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
/lib
/es
/umd
/types
/coverage
.scannerwork
9 changes: 7 additions & 2 deletions CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
1.10.0 (September XX, 2023)
1.10.0 (November XX, 2023)
- Added support for Flag Sets on the SDK, which enables grouping feature flags and interacting with the group rather than individually (more details in our documentation):
- Added a new `flagSets` prop to the `SplitTreatments` component and `flagSets` argument to the `useSplitTreatments` hook options object, to support evaluating flags in given flag set/s.
- Either `names` or `flagSets` must be provided to the component and hook. If both are provided, `names` will be used.
- Added a new optional Split Filter configuration option. This allows the SDK and Split services to only synchronize the flags in the specified flag sets, avoiding unused or unwanted flags from being synced on the SDK instance, bringing all the benefits from a reduced payload.
- Updated the following SDK manager methods to expose flag sets on flag views: `manager.splits()` and `manager.split()`.
- Added new `useSplitClient` and `useSplitTreatments` hooks to use instead of `useClient` and `useTreatments` respectively, which are deprecated now.
- The new hooks return the Split context object together with the SDK client and treatments respectively, rather than returning only the client and treatments as the deprecated hooks do. This way it is possible to access status properties, like `isReady`, from the hook's results, without having to use the `useContext` hook or the client `ready` promise.
- They accept an options object as parameter, which support the same arguments than the deprecated hooks, plus new boolean options to control when the hook should re-render: `updateOnSdkReady`, `updateOnSdkReadyFromCache`, `updateOnSdkTimedout`, and `updateOnSdkUpdate`.
- `useSplitTreatments` optimizes feature flag evaluations by using the `useMemo` hook to memoize calls to the SDK's `getTreatmentsWithConfig` method. This avoids re-evaluating feature flags when the hook is called with the same options and the feature flag definitions have not changed.
- They fixed a bug in the deprecated hooks, which caused them to not re-render and re-evaluate feature flags when they consume a different SDK client than the context and its status updates (i.e., when it emits SDK_READY or other event).
- Added TypeScript types and interfaces to the library index exports, allowing them to be imported from the library index. For example, `import type { ISplitFactoryProps } from '@splitsoftware/splitio-react';` (Related to issue https://github.com/splitio/react-client/issues/162).
- Updated type declarations of the library components to not restrict the type of the `children` prop to ReactElement, allowing to pass any valid ReactNode value (Related to issue https://github.com/splitio/react-client/issues/164).
- Updated the `useTreatments` hook to optimize feature flag evaluation. It now uses the `useMemo` hook to memoize calls to the SDK's `getTreatmentsWithConfig` function. This avoids re-evaluating feature flags when the hook is called with the same parameters and the feature flag definitions have not changed.
- Updated the `useTreatments` hook to optimize feature flag evaluations.
- Updated linter and other dependencies for vulnerability fixes.
- Bugfixing - To adhere to the rules of hooks and prevent React warnings, conditional code within hooks was removed. Previously, this code checked for the availability of the hooks API (available in React version 16.8.0 or above) and logged an error message. Now, using hooks with React versions below 16.8.0 will throw an error.
- Bugfixing - Updated `useClient` and `useTreatments` hooks to re-render and re-evaluate feature flags when they consume a different SDK client than the context and its status updates (i.e., when it emits SDK_READY or other event).
Expand Down
38 changes: 20 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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, SplitTreatments } from '@splitsoftware/splitio-react';
import { SplitFactory, useSplitTreatments } from '@splitsoftware/splitio-react';

// Define your config object
const CONFIG = {
Expand All @@ -30,25 +30,27 @@ const CONFIG = {
}
};

function MyReactComponent() {
function MyComponent() {
// Evaluate feature flags with useSplitTreatments hook
const { treatments: { FEATURE_FLAG_NAME }, isReady } = useSplitTreatments({ names: ['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
} else {
// return JSX for control treatment
};
}

function MyApp() {
return (
/* Use SplitFactory to instantiate the SDK and makes it available to nested components */
// Use SplitFactory to instantiate the SDK and makes it available to nested components
<SplitFactory config={CONFIG} >
{/* Evaluate feature flags with SplitTreatments component */}
<SplitTreatments names={['FEATURE_FLAG_NAME']} >
{({ treatments: { FEATURE_FLAG_NAME }, isReady }) => {
// 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
} else {
// return JSX for control treatment
}
}}
</SplitTreatments>
<MyComponent />
</SplitFactory>
);
}
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

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

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@splitsoftware/splitio-react",
"version": "1.9.0",
"version": "1.9.1-rc.0",
"description": "A React library to easily integrate and use Split JS SDK",
"main": "lib/index.js",
"module": "es/index.js",
Expand Down
4 changes: 2 additions & 2 deletions src/__tests__/SplitTreatments.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,8 @@ describe('SplitTreatments', () => {
}}
</SplitFactory>
);
expect(logSpy).toBeCalledWith('[ERROR] split names must be a non-empty array.');
expect(logSpy).toBeCalledWith('[ERROR] you passed an invalid split name, split name must be a non-empty string.');
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.');

done();
});
Expand Down
6 changes: 6 additions & 0 deletions src/__tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import {
useManager as exportedUseManager,
useTrack as exportedUseTrack,
useTreatments as exportedUseTreatments,
useSplitClient as exportedUseSplitClient,
useSplitTreatments as exportedUseSplitTreatments,
// Checks that types are exported. Otherwise, the test would fail with a TS error.
ISplitClientChildProps,
ISplitClientProps,
Expand All @@ -35,6 +37,8 @@ import { useClient } from '../useClient';
import { useManager } from '../useManager';
import { useTrack } from '../useTrack';
import { useTreatments } from '../useTreatments';
import { useSplitClient } from '../useSplitClient';
import { useSplitTreatments } from '../useSplitTreatments';

describe('index', () => {

Expand All @@ -55,6 +59,8 @@ describe('index', () => {
expect(exportedUseManager).toBe(useManager);
expect(exportedUseTrack).toBe(useTrack);
expect(exportedUseTreatments).toBe(useTreatments);
expect(exportedUseSplitClient).toBe(useSplitClient);
expect(exportedUseSplitTreatments).toBe(useSplitTreatments);
});

it('should export SplitContext', () => {
Expand Down
4 changes: 2 additions & 2 deletions src/__tests__/useSplitTreatments.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,8 @@ describe('useSplitTreatments', () => {
return null;
})
);
expect(logSpy).toBeCalledWith('[ERROR] split names must be a non-empty array.');
expect(logSpy).toBeCalledWith('[ERROR] you passed an invalid split name, split name must be a non-empty string.');
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.');
});

test('useSplitTreatments must update on SDK events', async () => {
Expand Down
32 changes: 18 additions & 14 deletions src/__tests__/withSplitTreatments.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,38 +12,42 @@ import { sdkBrowser } from './testUtils/sdkConfigs';
import { withSplitFactory } from '../withSplitFactory';
import { withSplitClient } from '../withSplitClient';
import { withSplitTreatments } from '../withSplitTreatments';
import { ISplitTreatmentsChildProps } from '../types';
import { getControlTreatmentsWithConfig } from '../constants';

describe('withSplitTreatments', () => {

it(`passes Split props and outer props to the child.
In this test, the value of "props.treatments" is obteined by the function "getControlTreatmentsWithConfig",
and not "client.getTreatmentsWithConfig" since the client is not ready.`, (done) => {
In this test, the value of "props.treatments" is obtained by the function "getControlTreatmentsWithConfig",
and not "client.getTreatmentsWithConfig" since the client is not ready.`, () => {
const featureFlagNames = ['split1', 'split2'];

const Component = withSplitFactory(sdkBrowser)<{ outerProp1: string, outerProp2: number }>(
({ outerProp1, outerProp2, factory }) => {
const SubComponent = withSplitClient('user1')<{ outerProp1: string, outerProp2: number }>(
withSplitTreatments(featureFlagNames)(
(props: ISplitTreatmentsChildProps & { outerProp1: string, outerProp2: number }) => {
(props) => {
const clientMock = factory!.client('user1');
expect(props.outerProp1).toBe('outerProp1');
expect(props.outerProp2).toBe(2);
expect((clientMock.getTreatmentsWithConfig as jest.Mock).mock.calls.length).toBe(0);
expect(props.treatments).toEqual(getControlTreatmentsWithConfig(featureFlagNames));
expect(props.isReady).toBe(false);
expect(props.isReadyFromCache).toBe(false);
expect(props.hasTimedout).toBe(false);
expect(props.isTimedout).toBe(false);
expect(props.isDestroyed).toBe(false);
expect(props.lastUpdate).toBe(0);
done();

expect(props).toStrictEqual({
factory: factory, client: clientMock,
outerProp1: 'outerProp1', outerProp2: 2,
treatments: getControlTreatmentsWithConfig(featureFlagNames),
isReady: false,
isReadyFromCache: false,
hasTimedout: false,
isTimedout: false,
isDestroyed: false,
lastUpdate: 0
});

return null;
}
)
);
return <SubComponent outerProp1={outerProp1} outerProp2={outerProp2} />;
});

render(<Component outerProp1='outerProp1' outerProp2={2} />);
});

Expand Down
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ export { useClient } from './useClient';
export { useTreatments } from './useTreatments';
export { useTrack } from './useTrack';
export { useManager } from './useManager';
export { useSplitClient } from './useSplitClient';
export { useSplitTreatments } from './useSplitTreatments';

// SplitContext
export { SplitContext } from './SplitContext';
Expand Down
4 changes: 2 additions & 2 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,8 +196,8 @@ export interface ISplitTreatmentsChildProps extends ISplitContextValues {
* An object with the treatments with configs for a bulk of feature flags, returned by client.getTreatmentsWithConfig().
* Each existing configuration is a stringified version of the JSON you defined on the Split user interface. For example:
* {
* split1: { treatment: 'on', config: null }
* split2: { treatment: 'off', config: '{"bannerText":"Click here."}' }
* feature1: { treatment: 'on', config: null }
* feature2: { treatment: 'off', config: '{"bannerText":"Click here."}' }
* }
*/
treatments: SplitIO.TreatmentsWithConfig;
Expand Down
2 changes: 2 additions & 0 deletions src/useClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { useSplitClient } from './useSplitClient';
*
* @return A Split Client instance, or null if used outside the scope of SplitFactory
* @see {@link https://help.split.io/hc/en-us/articles/360020448791-JavaScript-SDK#advanced-instantiate-multiple-sdk-clients}
*
* @deprecated Replace with the new `useSplitClient` hook.
*/
export function useClient(splitKey?: SplitIO.SplitKey, trafficType?: string, attributes?: SplitIO.Attributes): SplitIO.IBrowserClient | null {
return useSplitClient({ splitKey, trafficType, attributes }).client;
Expand Down
6 changes: 6 additions & 0 deletions src/useSplitClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ export const DEFAULT_UPDATE_OPTIONS = {
* It uses the 'useContext' hook to access the context, which is updated by SplitFactory and SplitClient components in the hierarchy of components.
*
* @return A Split Context object
*
* @example
* ```js
* const { factory, client, isReady, isReadyFromCache, hasTimedout, lastUpdate } = useSplitClient({ splitKey: 'user_id' });
* ```
*
* @see {@link https://help.split.io/hc/en-us/articles/360020448791-JavaScript-SDK#advanced-instantiate-multiple-sdk-clients}
*/
export function useSplitClient(options?: IUseSplitClientOptions): ISplitContextValues {
Expand Down
14 changes: 10 additions & 4 deletions src/useSplitTreatments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,17 @@ import { ISplitTreatmentsChildProps, IUseSplitTreatmentsOptions } from './types'
import { useSplitClient } from './useSplitClient';

/**
* 'useSplitTreatments' is a hook that returns a SplitContext object extended with a `treatments` property, which contains an object of feature flag evaluations (i.e., treatments).
* It utilizes the 'useSplitClient' hook to access the client from the context and invokes the 'getTreatmentsWithConfig' method
* if `names` property is provided, or the 'getTreatmentsWithConfigByFlagSets' method if `flagSets` property is provided.
* 'useSplitTreatments' is a hook that returns an SplitContext object extended with a `treatments` property object that contains feature flag evaluations.
* It uses the 'useSplitClient' hook to access the client from the Split context, and invokes the 'getTreatmentsWithConfig' method if `names` option is provided,
* or the 'getTreatmentsWithConfigByFlagSets' method if `flagSets` option is provided.
*
* @return A Split Context object extended with a TreatmentsWithConfig 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, ... } = useSplitTreatments({ names: ['feature_1', 'feature_2']});
* ```
*
* @return A Split Context object extended with a TreatmentsWithConfig instance, that might contain control treatments if the client is not available or ready, or if split names do not exist.
* @see {@link https://help.split.io/hc/en-us/articles/360020448791-JavaScript-SDK#get-treatments-with-configurations}
*/
export function useSplitTreatments(options: IUseSplitTreatmentsOptions): ISplitTreatmentsChildProps {
Expand Down
2 changes: 2 additions & 0 deletions src/useTreatments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { useSplitTreatments } from './useSplitTreatments';
*
* @return A TreatmentsWithConfig instance, that might contain control treatments if the client is not available or ready, or if feature flag names do not exist.
* @see {@link https://help.split.io/hc/en-us/articles/360020448791-JavaScript-SDK#get-treatments-with-configurations}
*
* @deprecated Replace with the new `useSplitTreatments` hook.
*/
export function useTreatments(featureFlagNames: string[], attributes?: SplitIO.Attributes, splitKey?: SplitIO.SplitKey): SplitIO.TreatmentsWithConfig {
return useSplitTreatments({ names: featureFlagNames, attributes, splitKey }).treatments;
Expand Down
4 changes: 2 additions & 2 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ export function getStatus(client: SplitIO.IBrowserClient | null): ISplitStatus {

// Input validation utils that will be replaced eventually

export function validateFeatureFlags(maybeFeatureFlags: unknown, listName = 'split names'): false | string[] {
export function validateFeatureFlags(maybeFeatureFlags: unknown, listName = 'feature flag names'): false | string[] {
if (Array.isArray(maybeFeatureFlags) && maybeFeatureFlags.length > 0) {
const validatedArray: string[] = [];
// Remove invalid values
Expand All @@ -125,7 +125,7 @@ export function initAttributes(client: SplitIO.IBrowserClient | null, attributes

const TRIMMABLE_SPACES_REGEX = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/;

function validateFeatureFlag(maybeFeatureFlag: unknown, item = 'split name'): false | string {
function validateFeatureFlag(maybeFeatureFlag: unknown, item = 'feature flag name'): false | string {
if (maybeFeatureFlag == undefined) {
console.log(`[ERROR] you passed a null or undefined ${item}, ${item} must be a non-empty string.`);
} else if (!isString(maybeFeatureFlag)) {
Expand Down
64 changes: 0 additions & 64 deletions types/SplitClient.d.ts

This file was deleted.

Loading

0 comments on commit 20091ca

Please sign in to comment.