From d15e1b3acdd30eea720fc58fad95c14a16bff5aa Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Thu, 2 Nov 2023 14:05:48 -0300 Subject: [PATCH] implementation of new hooks with options object --- package-lock.json | 12 +++---- src/__tests__/SplitTreatments.test.tsx | 2 +- src/__tests__/useSplitClient.test.tsx | 12 +++---- src/__tests__/useSplitTreatments.test.tsx | 6 ++-- src/types.ts | 43 ++++++++++++++++------- src/useClient.ts | 4 +-- src/useSplitClient.ts | 10 +++--- src/useSplitTreatments.ts | 13 +++---- src/useTrack.ts | 7 ++-- src/useTreatments.ts | 4 +-- types/types.d.ts | 36 +++++++++++++------ types/useClient.d.ts | 2 +- types/useSplitClient.d.ts | 4 +-- types/useSplitTreatments.d.ts | 4 +-- types/useTrack.d.ts | 2 +- types/useTreatments.d.ts | 2 +- 16 files changed, 100 insertions(+), 63 deletions(-) diff --git a/package-lock.json b/package-lock.json index cb6d8e7..5f01255 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10149,9 +10149,9 @@ } }, "node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "node_modules/type-check": { "version": "0.3.2", @@ -18581,9 +18581,9 @@ } }, "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "type-check": { "version": "0.3.2", diff --git a/src/__tests__/SplitTreatments.test.tsx b/src/__tests__/SplitTreatments.test.tsx index e7145a4..f3c2f56 100644 --- a/src/__tests__/SplitTreatments.test.tsx +++ b/src/__tests__/SplitTreatments.test.tsx @@ -159,7 +159,7 @@ describe.each([ ), ({ names, attributes }) => { - useSplitTreatments(names, attributes); + useSplitTreatments({ names, attributes }); renderTimes++; return null; } diff --git a/src/__tests__/useSplitClient.test.tsx b/src/__tests__/useSplitClient.test.tsx index 543032d..8551c7b 100644 --- a/src/__tests__/useSplitClient.test.tsx +++ b/src/__tests__/useSplitClient.test.tsx @@ -40,7 +40,7 @@ test('useSplitClient must update on SDK events', () => { // Equivalent to // - Using config key and traffic type: `const { client } = useSplitClient(sdkBrowser.core.key, sdkBrowser.core.trafficType, { att1: 'att1' });` // - Disabling update props, since the wrapping SplitFactory has them enabled: `const { client } = useSplitClient(undefined, undefined, { att1: 'att1' }, { updateOnSdkReady: false, updateOnSdkReadyFromCache: false });` - const { client } = useSplitClient(undefined, undefined, { att1: 'att1' }); + const { client } = useSplitClient({ attributes: { att1: 'att1' } }); expect(client).toBe(mainClient); // Assert that the main client was retrieved. expect(client!.getAttributes()).toEqual({ att1: 'att1' }); // Assert that the client was retrieved with the provided attributes. countUseSplitClient++; @@ -50,7 +50,7 @@ test('useSplitClient must update on SDK events', () => { {() => { countSplitClientUser2++; return null }} {React.createElement(() => { - const { client } = useSplitClient('user_2'); + const { client } = useSplitClient({ splitKey: 'user_2' }); expect(client).toBe(user2Client); countUseSplitClientUser2++; return null; @@ -59,7 +59,7 @@ test('useSplitClient must update on SDK events', () => { {() => { countSplitClientWithUpdate++; return null }} {React.createElement(() => { - useSplitClient(sdkBrowser.core.key, sdkBrowser.core.trafficType, undefined, { updateOnSdkUpdate: true }).client; + useSplitClient({ splitKey: sdkBrowser.core.key, trafficType: sdkBrowser.core.trafficType, updateOnSdkUpdate: true }).client; countUseSplitClientWithUpdate++; return null; })} @@ -67,13 +67,13 @@ test('useSplitClient must update on SDK events', () => { {() => { countSplitClientUser2WithUpdate++; return null }} {React.createElement(() => { - useSplitClient('user_2', undefined, undefined, { updateOnSdkUpdate: true }); + useSplitClient({ splitKey: 'user_2', updateOnSdkUpdate: true }); countUseSplitClientUser2WithUpdate++; return null; })} {React.createElement(() => { - const status = useSplitClient('user_2', undefined, undefined, { updateOnSdkUpdate: true }); + const status = useSplitClient({ splitKey: 'user_2', updateOnSdkUpdate: true }); expect(status.client).toBe(user2Client); // useSplitClient doesn't re-render twice if it is in the context of a SplitClient with same user key and there is a SDK event @@ -143,7 +143,7 @@ test('useSplitClient must support changes in update props', () => { let rendersCount = 0; function InnerComponent(updateOptions) { - useSplitClient(undefined, undefined, undefined, updateOptions); + useSplitClient(updateOptions); rendersCount++; return null; } diff --git a/src/__tests__/useSplitTreatments.test.tsx b/src/__tests__/useSplitTreatments.test.tsx index b89fbee..5fe57a9 100644 --- a/src/__tests__/useSplitTreatments.test.tsx +++ b/src/__tests__/useSplitTreatments.test.tsx @@ -51,21 +51,21 @@ test('useSplitTreatments must update on SDK events', async () => { {() => { countSplitTreatments++; return null }} {React.createElement(() => { - const context = useSplitTreatments(['split_test'], { att1: 'att1' }); + const context = useSplitTreatments({ names: ['split_test'], attributes: { att1: 'att1' } }); expect(context.client).toBe(mainClient); // Assert that the main client was retrieved. validateTreatments(context); countUseSplitTreatments++; return null; })} {React.createElement(() => { - const context = useSplitTreatments(['split_test'], undefined, 'user_2'); + const context = useSplitTreatments({ names: ['split_test'], splitKey: 'user_2' }); expect(context.client).toBe(user2Client); validateTreatments(context); countUseSplitTreatmentsUser2++; return null; })} {React.createElement(() => { - const context = useSplitTreatments(['split_test'], undefined, 'user_2', { updateOnSdkUpdate: true }); + const context = useSplitTreatments({ names: ['split_test'], splitKey: 'user_2', updateOnSdkUpdate: true }); expect(context.client).toBe(user2Client); validateTreatments(context); countUseSplitTreatmentsUser2WithUpdate++; diff --git a/src/types.ts b/src/types.ts index 8349c49..42d2a85 100644 --- a/src/types.ts +++ b/src/types.ts @@ -102,7 +102,7 @@ export interface ISplitFactoryChildProps extends ISplitContextValues { } /** * SplitFactory Props interface. These are the props accepted by SplitFactory component, - * used to instantiate a factory and client instances, update the Split context, and listen for SDK events. + * used to instantiate a factory and client instance, update the Split context, and listen for SDK events. */ export interface ISplitFactoryProps extends IUpdateProps { @@ -129,22 +129,15 @@ export interface ISplitFactoryProps extends IUpdateProps { } /** - * SplitClient Child Props interface. These are the props that the child component receives from the 'SplitClient' component. - */ -// @TODO remove next type (breaking-change) -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface ISplitClientChildProps extends ISplitContextValues { } - -/** - * SplitClient Props interface. These are the props accepted by SplitClient component, - * used to instantiate a new client instances, update the Split context, and listen for SDK events. + * useSplitClient options interface. This is the options object accepted by useSplitClient hook, + * used to retrieve a client instance with the Split context, and listen for SDK events. */ -export interface ISplitClientProps extends IUpdateProps { +export interface IUseSplitClientOptions extends IUpdateProps { /** * The customer identifier. */ - splitKey: SplitIO.SplitKey; + splitKey?: SplitIO.SplitKey; /** * Traffic type associated with the customer identifier. @@ -156,6 +149,20 @@ export interface ISplitClientProps extends IUpdateProps { * An object of type Attributes used to evaluate the feature flags. */ attributes?: SplitIO.Attributes; +} + +/** + * SplitClient Child Props interface. These are the props that the child component receives from the 'SplitClient' component. + */ +// @TODO remove next type (breaking-change) +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface ISplitClientChildProps extends ISplitContextValues { } + +/** + * SplitClient Props interface. These are the props accepted by SplitClient component, + * used to instantiate a new client instance, update the Split context, and listen for SDK events. + */ +export interface ISplitClientProps extends IUseSplitClientOptions { /** * Children of the SplitFactory component. It can be a functional component (child as a function) or a React element. @@ -163,6 +170,18 @@ export interface ISplitClientProps extends IUpdateProps { children: ((props: ISplitClientChildProps) => ReactNode) | ReactNode; } +/** + * useSplitTreatments options interface. This is the options object accepted by useSplitTreatments hook, + * used to call 'client.getTreatmentsWithConfig()' and retrieve the result together with the Split context. + */ +export interface IUseSplitTreatmentsOptions extends IUseSplitClientOptions { + + /** + * list of feature flag names + */ + names: string[] +} + /** * SplitTreatments Child Props interface. These are the props that the child component receives from the 'SplitTreatments' component. */ diff --git a/src/useClient.ts b/src/useClient.ts index ddafee0..437fc8a 100644 --- a/src/useClient.ts +++ b/src/useClient.ts @@ -8,6 +8,6 @@ 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} */ -export function useClient(key?: SplitIO.SplitKey, trafficType?: string, attributes?: SplitIO.Attributes): SplitIO.IBrowserClient | null { - return useSplitClient(key, trafficType, attributes).client; +export function useClient(splitKey?: SplitIO.SplitKey, trafficType?: string, attributes?: SplitIO.Attributes): SplitIO.IBrowserClient | null { + return useSplitClient({ splitKey, trafficType, attributes }).client; } diff --git a/src/useSplitClient.ts b/src/useSplitClient.ts index d5aa594..cd3a11c 100644 --- a/src/useSplitClient.ts +++ b/src/useSplitClient.ts @@ -1,7 +1,7 @@ import React from 'react'; import { SplitContext } from './SplitContext'; import { getSplitClient, initAttributes, IClientWithContext, getStatus } from './utils'; -import { ISplitContextValues, IUpdateProps } from './types'; +import { ISplitContextValues, IUseSplitClientOptions } from './types'; export const DEFAULT_UPDATE_OPTIONS = { updateOnSdkUpdate: false, @@ -17,17 +17,17 @@ export const DEFAULT_UPDATE_OPTIONS = { * @return A Split Context object * @see {@link https://help.split.io/hc/en-us/articles/360020448791-JavaScript-SDK#advanced-instantiate-multiple-sdk-clients} */ -export function useSplitClient(key?: SplitIO.SplitKey, trafficType?: string, attributes?: SplitIO.Attributes, options?: IUpdateProps): ISplitContextValues { +export function useSplitClient(options?: IUseSplitClientOptions): ISplitContextValues { const { - updateOnSdkReady, updateOnSdkReadyFromCache, updateOnSdkTimedout, updateOnSdkUpdate + updateOnSdkReady, updateOnSdkReadyFromCache, updateOnSdkTimedout, updateOnSdkUpdate, splitKey, trafficType, attributes } = { ...DEFAULT_UPDATE_OPTIONS, ...options }; const context = React.useContext(SplitContext); const { client: contextClient, factory } = context; let client = contextClient as IClientWithContext; - if (key && factory) { - client = getSplitClient(factory, key, trafficType); + if (splitKey && factory) { + client = getSplitClient(factory, splitKey, trafficType); } initAttributes(client, attributes); diff --git a/src/useSplitTreatments.ts b/src/useSplitTreatments.ts index ab4d196..ddc0590 100644 --- a/src/useSplitTreatments.ts +++ b/src/useSplitTreatments.ts @@ -1,7 +1,7 @@ import React from 'react'; import { getControlTreatmentsWithConfig } from './constants'; import { IClientWithContext, memoizeGetTreatmentsWithConfig } from './utils'; -import { ISplitTreatmentsChildProps, IUpdateProps } from './types'; +import { ISplitTreatmentsChildProps, IUseSplitTreatmentsOptions } from './types'; import { useSplitClient } from './useSplitClient'; /** @@ -11,15 +11,16 @@ import { useSplitClient } from './useSplitClient'; * @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(splitNames: string[], attributes?: SplitIO.Attributes, key?: SplitIO.SplitKey, options?: IUpdateProps): ISplitTreatmentsChildProps { - const context = useSplitClient(key, undefined, undefined, options); - const client = context.client; +export function useSplitTreatments(options: IUseSplitTreatmentsOptions): ISplitTreatmentsChildProps { + const context = useSplitClient({...options, attributes: undefined }); + const { client, lastUpdate } = context; + const { names, attributes } = options; const getTreatmentsWithConfig = React.useMemo(memoizeGetTreatmentsWithConfig, []); const treatments = client && (client as IClientWithContext).__getStatus().isOperational ? - getTreatmentsWithConfig(client, context.lastUpdate, splitNames, attributes, { ...client.getAttributes() }) : - getControlTreatmentsWithConfig(splitNames); + getTreatmentsWithConfig(client, lastUpdate, names, attributes, { ...client.getAttributes() }) : + getControlTreatmentsWithConfig(names); return { ...context, diff --git a/src/useTrack.ts b/src/useTrack.ts index 95b13df..108885e 100644 --- a/src/useTrack.ts +++ b/src/useTrack.ts @@ -1,4 +1,4 @@ -import { useClient } from './useClient'; +import { useSplitClient } from './useSplitClient'; // no-op function that returns false const noOpFalse = () => false; @@ -10,7 +10,8 @@ const noOpFalse = () => false; * @return A track function bound to a Split client. If the client is not available, the result is a no-op function that returns false. * @see {@link https://help.split.io/hc/en-us/articles/360020448791-JavaScript-SDK#track} */ -export function useTrack(key?: SplitIO.SplitKey, trafficType?: string): SplitIO.IBrowserClient['track'] { - const client = useClient(key, trafficType); +export function useTrack(splitKey?: SplitIO.SplitKey, trafficType?: string): SplitIO.IBrowserClient['track'] { + // All update options are false to avoid re-renders. The track method doesn't need the client to be operational. + const { client } = useSplitClient({ splitKey, trafficType, updateOnSdkReady: false, updateOnSdkReadyFromCache: false }); return client ? client.track.bind(client) : noOpFalse; } diff --git a/src/useTreatments.ts b/src/useTreatments.ts index 4aed7d8..3a152a5 100644 --- a/src/useTreatments.ts +++ b/src/useTreatments.ts @@ -8,6 +8,6 @@ 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} */ -export function useTreatments(featureFlagNames: string[], attributes?: SplitIO.Attributes, key?: SplitIO.SplitKey): SplitIO.TreatmentsWithConfig { - return useSplitTreatments(featureFlagNames, attributes, key).treatments; +export function useTreatments(featureFlagNames: string[], attributes?: SplitIO.Attributes, splitKey?: SplitIO.SplitKey): SplitIO.TreatmentsWithConfig { + return useSplitTreatments({ names: featureFlagNames, attributes, splitKey }).treatments; } diff --git a/types/types.d.ts b/types/types.d.ts index 7a35cdc..0e4c275 100644 --- a/types/types.d.ts +++ b/types/types.d.ts @@ -84,7 +84,7 @@ export interface ISplitFactoryChildProps extends ISplitContextValues { } /** * SplitFactory Props interface. These are the props accepted by SplitFactory component, - * used to instantiate a factory and client instances, update the Split context, and listen for SDK events. + * used to instantiate a factory and client instance, update the Split context, and listen for SDK events. */ export interface ISplitFactoryProps extends IUpdateProps { /** @@ -106,19 +106,14 @@ export interface ISplitFactoryProps extends IUpdateProps { children: ((props: ISplitFactoryChildProps) => ReactNode) | ReactNode; } /** - * SplitClient Child Props interface. These are the props that the child component receives from the 'SplitClient' component. - */ -export interface ISplitClientChildProps extends ISplitContextValues { -} -/** - * SplitClient Props interface. These are the props accepted by SplitClient component, - * used to instantiate a new client instances, update the Split context, and listen for SDK events. + * useSplitClient options interface. This is the options object accepted by useSplitClient hook, + * used to retrieve a client instance with the Split context, and listen for SDK events. */ -export interface ISplitClientProps extends IUpdateProps { +export interface IUseSplitClientOptions extends IUpdateProps { /** * The customer identifier. */ - splitKey: SplitIO.SplitKey; + splitKey?: SplitIO.SplitKey; /** * Traffic type associated with the customer identifier. * If no provided here or at the config object, it will be required on the client.track() calls. @@ -128,11 +123,32 @@ export interface ISplitClientProps extends IUpdateProps { * An object of type Attributes used to evaluate the feature flags. */ attributes?: SplitIO.Attributes; +} +/** + * SplitClient Child Props interface. These are the props that the child component receives from the 'SplitClient' component. + */ +export interface ISplitClientChildProps extends ISplitContextValues { +} +/** + * SplitClient Props interface. These are the props accepted by SplitClient component, + * used to instantiate a new client instance, update the Split context, and listen for SDK events. + */ +export interface ISplitClientProps extends IUseSplitClientOptions { /** * Children of the SplitFactory component. It can be a functional component (child as a function) or a React element. */ children: ((props: ISplitClientChildProps) => ReactNode) | ReactNode; } +/** + * useSplitTreatments options interface. This is the options object accepted by useSplitTreatments hook, + * used to call 'client.getTreatmentsWithConfig()' and retrieve the result together with the Split context. + */ +export interface IUseSplitTreatmentsOptions extends IUseSplitClientOptions { + /** + * list of feature flag names + */ + names: string[]; +} /** * SplitTreatments Child Props interface. These are the props that the child component receives from the 'SplitTreatments' component. */ diff --git a/types/useClient.d.ts b/types/useClient.d.ts index 75284f7..d08dbf4 100644 --- a/types/useClient.d.ts +++ b/types/useClient.d.ts @@ -6,4 +6,4 @@ * @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} */ -export declare function useClient(key?: SplitIO.SplitKey, trafficType?: string, attributes?: SplitIO.Attributes): SplitIO.IBrowserClient | null; +export declare function useClient(splitKey?: SplitIO.SplitKey, trafficType?: string, attributes?: SplitIO.Attributes): SplitIO.IBrowserClient | null; diff --git a/types/useSplitClient.d.ts b/types/useSplitClient.d.ts index 761f787..90868e4 100644 --- a/types/useSplitClient.d.ts +++ b/types/useSplitClient.d.ts @@ -1,4 +1,4 @@ -import { ISplitContextValues, IUpdateProps } from './types'; +import { ISplitContextValues, IUseSplitClientOptions } from './types'; export declare const DEFAULT_UPDATE_OPTIONS: { updateOnSdkUpdate: boolean; updateOnSdkTimedout: boolean; @@ -12,4 +12,4 @@ export declare const DEFAULT_UPDATE_OPTIONS: { * @return A Split Context object * @see {@link https://help.split.io/hc/en-us/articles/360020448791-JavaScript-SDK#advanced-instantiate-multiple-sdk-clients} */ -export declare function useSplitClient(key?: SplitIO.SplitKey, trafficType?: string, attributes?: SplitIO.Attributes, options?: IUpdateProps): ISplitContextValues; +export declare function useSplitClient(options?: IUseSplitClientOptions): ISplitContextValues; diff --git a/types/useSplitTreatments.d.ts b/types/useSplitTreatments.d.ts index edbda7f..b636632 100644 --- a/types/useSplitTreatments.d.ts +++ b/types/useSplitTreatments.d.ts @@ -1,4 +1,4 @@ -import { ISplitTreatmentsChildProps, IUpdateProps } from './types'; +import { ISplitTreatmentsChildProps, IUseSplitTreatmentsOptions } from './types'; /** * 'useSplitTreatments' is a hook that returns an SplitContext object extended with a `treatments` property containing an object of feature flag evaluations (i.e., treatments). * It uses the 'useSplitClient' hook to access the client from the Split context, and invokes the 'getTreatmentsWithConfig' method. @@ -6,4 +6,4 @@ import { ISplitTreatmentsChildProps, IUpdateProps } from './types'; * @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 declare function useSplitTreatments(splitNames: string[], attributes?: SplitIO.Attributes, key?: SplitIO.SplitKey, options?: IUpdateProps): ISplitTreatmentsChildProps; +export declare function useSplitTreatments(options: IUseSplitTreatmentsOptions): ISplitTreatmentsChildProps; diff --git a/types/useTrack.d.ts b/types/useTrack.d.ts index e983136..ff25094 100644 --- a/types/useTrack.d.ts +++ b/types/useTrack.d.ts @@ -5,4 +5,4 @@ * @return A track function bound to a Split client. If the client is not available, the result is a no-op function that returns false. * @see {@link https://help.split.io/hc/en-us/articles/360020448791-JavaScript-SDK#track} */ -export declare function useTrack(key?: SplitIO.SplitKey, trafficType?: string): SplitIO.IBrowserClient['track']; +export declare function useTrack(splitKey?: SplitIO.SplitKey, trafficType?: string): SplitIO.IBrowserClient['track']; diff --git a/types/useTreatments.d.ts b/types/useTreatments.d.ts index 6b688e7..fd8170e 100644 --- a/types/useTreatments.d.ts +++ b/types/useTreatments.d.ts @@ -6,4 +6,4 @@ * @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} */ -export declare function useTreatments(featureFlagNames: string[], attributes?: SplitIO.Attributes, key?: SplitIO.SplitKey): SplitIO.TreatmentsWithConfig; +export declare function useTreatments(featureFlagNames: string[], attributes?: SplitIO.Attributes, splitKey?: SplitIO.SplitKey): SplitIO.TreatmentsWithConfig;