Skip to content

Commit 054513a

Browse files
Merge pull request #234 from splitio/fix_update_options_toggle
Fix: toggling `updateOnSdkUpdate` option
2 parents 83abcc5 + 02a26be commit 054513a

File tree

3 files changed

+40
-15
lines changed

3 files changed

+40
-15
lines changed

CHANGES.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
2.1.1 (April 8, 2025)
2-
- Bugfix - Fixed `useSplitClient` and `useSplitTreatments` hooks to properly respect `updateOn<Event>` options. Previously, if the hooks were re-called due to a component re-render, they used the latest version of the SDK client status ignoring when `updateOn<Event>` options were set to `false` and resulting in unexpected changes in treatment values.
2+
- Bugfixing - Fixed `useSplitClient` and `useSplitTreatments` hooks to properly respect `updateOn<Event>` options. Previously, if the hooks were re-called due to a component re-render, they used the latest version of the SDK client status ignoring when `updateOn<Event>` options were set to `false` and resulting in unexpected changes in treatment values.
33

44
2.1.0 (March 28, 2025)
55
- Added a new optional `properties` argument to the options object of the `useSplitTreatments` hook, allowing to pass a map of properties to append to the generated impressions sent to Split backend. Read more in our docs.

src/__tests__/useSplitClient.test.tsx

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { sdkBrowser } from './testUtils/sdkConfigs';
1313
import { useSplitClient } from '../useSplitClient';
1414
import { SplitFactoryProvider } from '../SplitFactoryProvider';
1515
import { SplitContext } from '../SplitContext';
16-
import { testAttributesBinding, TestComponentProps } from './testUtils/utils';
16+
import { INITIAL_STATUS, testAttributesBinding, TestComponentProps } from './testUtils/utils';
1717
import { EXCEPTION_NO_SFP } from '../constants';
1818

1919
describe('useSplitClient', () => {
@@ -222,9 +222,11 @@ describe('useSplitClient', () => {
222222
const mainClient = outerFactory.client() as any;
223223

224224
let rendersCount = 0;
225+
let currentStatus, previousStatus;
225226

226227
function InnerComponent(updateOptions) {
227-
useSplitClient(updateOptions);
228+
previousStatus = currentStatus;
229+
currentStatus = useSplitClient(updateOptions);
228230
rendersCount++;
229231
return null;
230232
}
@@ -237,26 +239,46 @@ describe('useSplitClient', () => {
237239
)
238240
}
239241

240-
const wrapper = render(<Component updateOnSdkUpdate={false} />);
242+
const wrapper = render(<Component updateOnSdkUpdate={false} updateOnSdkTimedout={false} />);
241243
expect(rendersCount).toBe(1);
242244

243-
act(() => mainClient.__emitter__.emit(Event.SDK_READY)); // trigger re-render
244-
expect(rendersCount).toBe(2);
245+
act(() => mainClient.__emitter__.emit(Event.SDK_READY_TIMED_OUT)); // do not trigger re-render because updateOnSdkTimedout is false
246+
expect(rendersCount).toBe(1);
247+
expect(currentStatus).toMatchObject(INITIAL_STATUS);
245248

246-
act(() => mainClient.__emitter__.emit(Event.SDK_UPDATE)); // do not trigger re-render because updateOnSdkUpdate is false
249+
wrapper.rerender(<Component updateOnSdkUpdate={false} updateOnSdkTimedout={false} />);
247250
expect(rendersCount).toBe(2);
251+
expect(currentStatus).toEqual(previousStatus);
248252

249-
wrapper.rerender(<Component updateOnSdkUpdate={null /** invalid type should default to `true` */} />); // trigger re-render
250-
expect(rendersCount).toBe(3);
251-
252-
act(() => mainClient.__emitter__.emit(Event.SDK_UPDATE)); // trigger re-render because updateOnSdkUpdate is true now
253+
wrapper.rerender(<Component updateOnSdkUpdate={false} updateOnSdkTimedout={true} />); // trigger re-render because there was an SDK_READY_TIMED_OUT event
253254
expect(rendersCount).toBe(4);
255+
expect(currentStatus).toMatchObject({ isReady: false, isReadyFromCache: false, hasTimedout: true });
254256

255-
wrapper.rerender(<Component updateOnSdkUpdate={false} />); // trigger re-render
257+
act(() => mainClient.__emitter__.emit(Event.SDK_READY)); // trigger re-render
256258
expect(rendersCount).toBe(5);
259+
expect(currentStatus).toMatchObject({ isReady: true, isReadyFromCache: false, hasTimedout: true });
257260

258-
act(() => mainClient.__emitter__.emit(Event.SDK_UPDATE)); // do not trigger re-render because updateOnSdkUpdate is false now
261+
act(() => mainClient.__emitter__.emit(Event.SDK_UPDATE)); // do not trigger re-render because updateOnSdkUpdate is false
259262
expect(rendersCount).toBe(5);
263+
264+
wrapper.rerender(<Component updateOnSdkUpdate={false} updateOnSdkTimedout={false} />); // should not update the status (SDK_UPDATE event should be ignored)
265+
expect(rendersCount).toBe(6);
266+
expect(currentStatus).toEqual(previousStatus);
267+
268+
wrapper.rerender(<Component updateOnSdkUpdate={null /** invalid type should default to `true` */} />); // trigger re-render and update the status because updateOnSdkUpdate is true and there was an SDK_UPDATE event
269+
expect(rendersCount).toBe(8);
270+
expect(currentStatus.lastUpdate).toBeGreaterThan(previousStatus.lastUpdate);
271+
272+
act(() => mainClient.__emitter__.emit(Event.SDK_UPDATE)); // trigger re-render because updateOnSdkUpdate is true
273+
expect(rendersCount).toBe(9);
274+
expect(currentStatus.lastUpdate).toBeGreaterThan(previousStatus.lastUpdate);
275+
276+
wrapper.rerender(<Component updateOnSdkUpdate={false} />);
277+
expect(rendersCount).toBe(10);
278+
expect(currentStatus).toEqual(previousStatus);
279+
280+
act(() => mainClient.__emitter__.emit(Event.SDK_UPDATE)); // do not trigger re-render because updateOnSdkUpdate is false now
281+
expect(rendersCount).toBe(10);
260282
});
261283

262284
});

src/useSplitClient.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export function useSplitClient(options?: IUseSplitClientOptions): ISplitContextV
3131
const context = useSplitContext();
3232
const { client: contextClient, factory } = context;
3333

34-
// @TODO Move `getSplitClient` side effects
34+
// @TODO Move `getSplitClient` side effects and reduce the function cognitive complexity
3535
// @TODO Once `SplitClient` is removed, which updates the context, simplify next line as `const client = factory ? getSplitClient(factory, splitKey) : undefined;`
3636
const client = factory && splitKey ? getSplitClient(factory, splitKey) : contextClient;
3737

@@ -68,7 +68,10 @@ export function useSplitClient(options?: IUseSplitClientOptions): ISplitContextV
6868
if (!status.hasTimedout) update();
6969
}
7070
}
71-
if (updateOnSdkUpdate !== false) client.on(client.Event.SDK_UPDATE, update);
71+
if (updateOnSdkUpdate !== false) {
72+
client.on(client.Event.SDK_UPDATE, update);
73+
if (statusOnEffect.isReady && statusOnEffect.lastUpdate > status.lastUpdate) update();
74+
}
7275

7376
return () => {
7477
// Unsubscribe from events

0 commit comments

Comments
 (0)