Skip to content

Commit

Permalink
implementation of new hooks with options object
Browse files Browse the repository at this point in the history
  • Loading branch information
EmilianoSanchez committed Nov 2, 2023
1 parent f41f1c6 commit d15e1b3
Show file tree
Hide file tree
Showing 16 changed files with 100 additions and 63 deletions.
12 changes: 6 additions & 6 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 src/__tests__/SplitTreatments.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ describe.each([
</SplitTreatments>
),
({ names, attributes }) => {
useSplitTreatments(names, attributes);
useSplitTreatments({ names, attributes });
renderTimes++;
return null;
}
Expand Down
12 changes: 6 additions & 6 deletions src/__tests__/useSplitClient.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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++;
Expand All @@ -50,7 +50,7 @@ test('useSplitClient must update on SDK events', () => {
{() => { countSplitClientUser2++; return null }}
</SplitClient>
{React.createElement(() => {
const { client } = useSplitClient('user_2');
const { client } = useSplitClient({ splitKey: 'user_2' });
expect(client).toBe(user2Client);
countUseSplitClientUser2++;
return null;
Expand All @@ -59,21 +59,21 @@ test('useSplitClient must update on SDK events', () => {
{() => { countSplitClientWithUpdate++; return null }}
</SplitClient>
{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;
})}
<SplitClient splitKey={'user_2'} updateOnSdkUpdate={true}>
{() => { countSplitClientUser2WithUpdate++; return null }}
</SplitClient>
{React.createElement(() => {
useSplitClient('user_2', undefined, undefined, { updateOnSdkUpdate: true });
useSplitClient({ splitKey: 'user_2', updateOnSdkUpdate: true });
countUseSplitClientUser2WithUpdate++;
return null;
})}
<SplitClient splitKey={'user_2'} updateOnSdkUpdate={true}>
{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
Expand Down Expand Up @@ -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;
}
Expand Down
6 changes: 3 additions & 3 deletions src/__tests__/useSplitTreatments.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,21 +51,21 @@ test('useSplitTreatments must update on SDK events', async () => {
{() => { countSplitTreatments++; return null }}
</SplitTreatments>
{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++;
Expand Down
43 changes: 31 additions & 12 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand All @@ -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.
Expand All @@ -156,13 +149,39 @@ 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.
*/
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.
*/
Expand Down
4 changes: 2 additions & 2 deletions src/useClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
10 changes: 5 additions & 5 deletions src/useSplitClient.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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);

Expand Down
13 changes: 7 additions & 6 deletions src/useSplitTreatments.ts
Original file line number Diff line number Diff line change
@@ -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';

/**
Expand All @@ -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,
Expand Down
7 changes: 4 additions & 3 deletions src/useTrack.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useClient } from './useClient';
import { useSplitClient } from './useSplitClient';

// no-op function that returns false
const noOpFalse = () => false;
Expand All @@ -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;
}
4 changes: 2 additions & 2 deletions src/useTreatments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
36 changes: 26 additions & 10 deletions types/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
/**
Expand All @@ -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.
Expand All @@ -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.
*/
Expand Down
2 changes: 1 addition & 1 deletion types/useClient.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
4 changes: 2 additions & 2 deletions types/useSplitClient.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ISplitContextValues, IUpdateProps } from './types';
import { ISplitContextValues, IUseSplitClientOptions } from './types';
export declare const DEFAULT_UPDATE_OPTIONS: {
updateOnSdkUpdate: boolean;
updateOnSdkTimedout: boolean;
Expand All @@ -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;
4 changes: 2 additions & 2 deletions types/useSplitTreatments.d.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
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.
*
* @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;
Loading

0 comments on commit d15e1b3

Please sign in to comment.