diff --git a/CHANGES.txt b/CHANGES.txt index 6236b6a..bd0383b 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,14 +1,14 @@ 1.10.0 (November XX, 2023) - - 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). + - Added new `useSplitClient`, `useSplitTreatments` and `useSplitManager` hooks as replacements for the now deprecated `useClient`, `useTreatments` and `useManager` hooks. + - These new hooks return the Split context object along with the SDK client, treatments and manager respectively, enabling direct access to status properties like `isReady`, eliminating the need for using the `useContext` hook or the client's `ready` promise. + - `useSplitClient` and `useSplitTreatments` accept an options object as parameter, which support the same arguments as their predecessors, with additional boolean options for controlling re-rendering: `updateOnSdkReady`, `updateOnSdkReadyFromCache`, `updateOnSdkTimedout`, and `updateOnSdkUpdate`. + - `useSplitTreatments` optimizes feature flag evaluations by using the `useMemo` hook to memoize `getTreatmentsWithConfig` method calls from the SDK. 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 `useClient` and `useTreatments` hooks, which caused them to not re-render and re-evaluate feature flags when they access 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, e.g., `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 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 - Removed conditional code within hooks to adhere to the rules of hooks and prevent React warnings. 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). 1.9.0 (July 18, 2023) diff --git a/src/__tests__/index.test.ts b/src/__tests__/index.test.ts index 3cc9a1b..3604c01 100644 --- a/src/__tests__/index.test.ts +++ b/src/__tests__/index.test.ts @@ -14,6 +14,7 @@ import { useTreatments as exportedUseTreatments, useSplitClient as exportedUseSplitClient, useSplitTreatments as exportedUseSplitTreatments, + useSplitManager as exportedUseSplitManager, // Checks that types are exported. Otherwise, the test would fail with a TS error. ISplitClientChildProps, ISplitClientProps, @@ -39,6 +40,7 @@ import { useTrack } from '../useTrack'; import { useTreatments } from '../useTreatments'; import { useSplitClient } from '../useSplitClient'; import { useSplitTreatments } from '../useSplitTreatments'; +import { useSplitManager } from '../useSplitManager'; describe('index', () => { @@ -61,6 +63,7 @@ describe('index', () => { expect(exportedUseTreatments).toBe(useTreatments); expect(exportedUseSplitClient).toBe(useSplitClient); expect(exportedUseSplitTreatments).toBe(useSplitTreatments); + expect(exportedUseSplitManager).toBe(useSplitManager); }); it('should export SplitContext', () => { diff --git a/src/__tests__/useManager.test.tsx b/src/__tests__/useManager.test.tsx index 041c7b4..ba4c36c 100644 --- a/src/__tests__/useManager.test.tsx +++ b/src/__tests__/useManager.test.tsx @@ -1,43 +1,21 @@ -import React from 'react'; -import { render } from '@testing-library/react'; - /** Mocks */ -import { mockSdk } from './testUtils/mockSplitSdk'; -jest.mock('@splitsoftware/splitio/client', () => { - return { SplitFactory: mockSdk() }; -}); -import { SplitFactory as SplitSdk } from '@splitsoftware/splitio/client'; -import { sdkBrowser } from './testUtils/sdkConfigs'; +const useSplitManagerMock = jest.fn(); +jest.mock('../useSplitManager', () => ({ + useSplitManager: useSplitManagerMock +})); /** Test target */ -import { SplitFactory } from '../SplitFactory'; import { useManager } from '../useManager'; describe('useManager', () => { - test('returns the factory manager from the Split context.', () => { - const outerFactory = SplitSdk(sdkBrowser); - let manager; - render( - - {React.createElement(() => { - manager = useManager(); - return null; - })} - - ); - expect(manager).toBe(outerFactory.manager()); - }); + test('calls useSplitManager with the correct arguments and returns the manager.', () => { + const manager = 'manager'; + useSplitManagerMock.mockReturnValue({ manager, isReady: false }); + + expect(useManager()).toBe(manager); - test('returns null if invoked outside Split context.', () => { - let manager; - render( - React.createElement(() => { - manager = useManager(); - return null; - }) - ); - expect(manager).toBe(null); + expect(useSplitManagerMock).toHaveBeenCalledTimes(1); }); }); diff --git a/src/__tests__/useSplitManager.test.tsx b/src/__tests__/useSplitManager.test.tsx new file mode 100644 index 0000000..9ec5367 --- /dev/null +++ b/src/__tests__/useSplitManager.test.tsx @@ -0,0 +1,80 @@ +import React from 'react'; +import { act, render } from '@testing-library/react'; + +/** Mocks */ +import { Event, mockSdk } from './testUtils/mockSplitSdk'; +jest.mock('@splitsoftware/splitio/client', () => { + return { SplitFactory: mockSdk() }; +}); +import { SplitFactory as SplitSdk } from '@splitsoftware/splitio/client'; +import { sdkBrowser } from './testUtils/sdkConfigs'; +import { getStatus } from '../utils'; + +/** Test target */ +import { SplitFactory } from '../SplitFactory'; +import { useSplitManager } from '../useSplitManager'; + +describe('useSplitManager', () => { + + test('returns the factory manager from the Split context, and updates when the context changes.', () => { + const outerFactory = SplitSdk(sdkBrowser); + let hookResult; + render( + + {React.createElement(() => { + hookResult = useSplitManager(); + return null; + })} + + ); + + expect(hookResult).toStrictEqual({ + manager: outerFactory.manager(), + client: outerFactory.client(), + factory: outerFactory, + hasTimedout: false, + isDestroyed: false, + isReady: false, + isReadyFromCache: false, + isTimedout: false, + lastUpdate: 0, + }); + + act(() => (outerFactory.client() as any).__emitter__.emit(Event.SDK_READY)); + + expect(hookResult).toStrictEqual({ + manager: outerFactory.manager(), + client: outerFactory.client(), + factory: outerFactory, + hasTimedout: false, + isDestroyed: false, + isReady: true, + isReadyFromCache: false, + isTimedout: false, + lastUpdate: getStatus(outerFactory.client()).lastUpdate, + }); + }); + + test('returns null if invoked outside Split context.', () => { + let hookResult; + render( + React.createElement(() => { + hookResult = useSplitManager(); + return null; + }) + ); + + expect(hookResult).toStrictEqual({ + manager: null, + client: null, + factory: null, + hasTimedout: false, + isDestroyed: false, + isReady: false, + isReadyFromCache: false, + isTimedout: false, + lastUpdate: 0, + }); + }); + +}); diff --git a/src/index.ts b/src/index.ts index ce3726c..6aff505 100644 --- a/src/index.ts +++ b/src/index.ts @@ -11,13 +11,14 @@ export { SplitTreatments } from './SplitTreatments'; export { SplitClient } from './SplitClient'; export { SplitFactory } from './SplitFactory'; -// helper functions/hooks +// Hooks export { useClient } from './useClient'; export { useTreatments } from './useTreatments'; export { useTrack } from './useTrack'; export { useManager } from './useManager'; export { useSplitClient } from './useSplitClient'; export { useSplitTreatments } from './useSplitTreatments'; +export { useSplitManager } from './useSplitManager'; // SplitContext export { SplitContext } from './SplitContext'; diff --git a/src/useClient.ts b/src/useClient.ts index 7f41c58..535eb98 100644 --- a/src/useClient.ts +++ b/src/useClient.ts @@ -5,7 +5,8 @@ import { useSplitClient } from './useSplitClient'; * 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 Client instance, or null if used outside the scope of SplitFactory + * @returns 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. diff --git a/src/useManager.ts b/src/useManager.ts index beceb38..9ba101b 100644 --- a/src/useManager.ts +++ b/src/useManager.ts @@ -1,15 +1,16 @@ -import React from 'react'; -import { SplitContext } from './SplitContext'; +import { useSplitManager } from './useSplitManager'; /** * 'useManager' is a hook that returns the Manager instance from the Split factory. * It uses the 'useContext' hook to access the factory at Split context, which is updated by * the SplitFactory component. * - * @return A Split Manager instance, or null if used outside the scope of SplitFactory + * @returns A Split Manager instance, or null if used outside the scope of SplitFactory + * * @see {@link https://help.split.io/hc/en-us/articles/360020448791-JavaScript-SDK#manager} + * + * @deprecated Replace with the new `useSplitManager` hook. */ export function useManager(): SplitIO.IManager | null { - const { factory } = React.useContext(SplitContext); - return factory ? factory.manager() : null; + return useSplitManager().manager; } diff --git a/src/useSplitClient.ts b/src/useSplitClient.ts index 5e2e6a6..a0c696c 100644 --- a/src/useSplitClient.ts +++ b/src/useSplitClient.ts @@ -14,7 +14,7 @@ export const DEFAULT_UPDATE_OPTIONS = { * 'useSplitClient' is a hook that returns an Split Context object with the client and its status corresponding to the provided key and trafficType. * 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 + * @returns A Split Context object * * @example * ```js diff --git a/src/useSplitManager.ts b/src/useSplitManager.ts new file mode 100644 index 0000000..1ba6a23 --- /dev/null +++ b/src/useSplitManager.ts @@ -0,0 +1,25 @@ +import React from 'react'; +import { SplitContext } from './SplitContext'; +import { ISplitContextValues } from './types'; + +/** + * 'useSplitManager' is a hook that returns an Split Context object with the Manager instance from the Split factory. + * It uses the 'useContext' hook to access the factory at Split context, which is updated by the SplitFactory component. + * + * @returns An object containing the Split context and the Split Manager instance, which is null if used outside the scope of SplitFactory + * + * @example + * ```js + * const { manager, isReady } = useSplitManager(); + * ``` + * + * @see {@link https://help.split.io/hc/en-us/articles/360020448791-JavaScript-SDK#manager} + */ +export function useSplitManager(): ISplitContextValues & { manager: SplitIO.IManager | null } { + // Update options are not supported, because updates can be controlled at the SplitFactory component. + const context = React.useContext(SplitContext); + return { + ...context, + manager: context.factory ? context.factory.manager() : null + }; +} diff --git a/src/useSplitTreatments.ts b/src/useSplitTreatments.ts index 56fb0db..0d68615 100644 --- a/src/useSplitTreatments.ts +++ b/src/useSplitTreatments.ts @@ -8,7 +8,7 @@ import { useSplitClient } from './useSplitClient'; * '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. * - * @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. + * @returns 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 diff --git a/src/useTrack.ts b/src/useTrack.ts index 108885e..b511018 100644 --- a/src/useTrack.ts +++ b/src/useTrack.ts @@ -7,7 +7,8 @@ const noOpFalse = () => false; * 'useTrack' is a hook that returns the track method from a Split client. * It uses the 'useContext' hook to access the client from the Split context. * - * @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. + * @returns 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(splitKey?: SplitIO.SplitKey, trafficType?: string): SplitIO.IBrowserClient['track'] { diff --git a/src/useTreatments.ts b/src/useTreatments.ts index a794eb6..bd673fa 100644 --- a/src/useTreatments.ts +++ b/src/useTreatments.ts @@ -5,7 +5,8 @@ import { useSplitTreatments } from './useSplitTreatments'; * It uses the 'useContext' hook to access the client from the Split context, * and invokes the 'getTreatmentsWithConfig' method. * - * @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. + * @returns 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.