Skip to content

Commit 1504d59

Browse files
Optimize SplitFactoryProvider: do not recreate factory if only updateOn props change
1 parent 561d83d commit 1504d59

File tree

2 files changed

+60
-27
lines changed

2 files changed

+60
-27
lines changed

src/SplitFactoryProvider.tsx

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import React from 'react';
33
import { SplitComponent } from './SplitClient';
44
import { ISplitFactoryProps } from './types';
55
import { WARN_SF_CONFIG_AND_FACTORY } from './constants';
6-
import { getSplitFactory, destroySplitFactory, IFactoryWithClients, getSplitClient, getStatus } from './utils';
6+
import { getSplitFactory, destroySplitFactory, IFactoryWithClients, getSplitClient, getStatus, __factories } from './utils';
77
import { DEFAULT_UPDATE_OPTIONS } from './useSplitClient';
88

99
/**
@@ -27,24 +27,39 @@ export function SplitFactoryProvider(props: ISplitFactoryProps) {
2727
config = undefined;
2828
}
2929

30-
const [stateFactory, setStateFactory] = React.useState(propFactory || null);
31-
const factory = propFactory || (stateFactory && config === (stateFactory as IFactoryWithClients).config ? stateFactory : null);
30+
const [configFactory, setConfigFactory] = React.useState<IFactoryWithClients | null>(null);
31+
const factory = propFactory || (configFactory && config === configFactory.config ? configFactory : null);
3232
const client = factory ? getSplitClient(factory) : null;
3333

34+
// Effect to initialize and destroy the factory
3435
React.useEffect(() => {
3536
if (config) {
3637
const factory = getSplitFactory(config);
38+
39+
return () => {
40+
destroySplitFactory(factory);
41+
}
42+
}
43+
}, [config]);
44+
45+
// Effect to subscribe/unsubscribe to events
46+
React.useEffect(() => {
47+
const factory = config && __factories.get(config);
48+
if (factory) {
3749
const client = getSplitClient(factory);
3850
const status = getStatus(client);
3951

40-
// Update state and unsubscribe from events when first event is emitted
41-
const update = () => {
52+
// Unsubscribe from events and update state when first event is emitted
53+
const update = () => { // eslint-disable-next-line no-use-before-define
54+
unsubscribe();
55+
setConfigFactory(factory);
56+
}
57+
58+
const unsubscribe = () => {
4259
client.off(client.Event.SDK_READY, update);
4360
client.off(client.Event.SDK_READY_FROM_CACHE, update);
4461
client.off(client.Event.SDK_READY_TIMED_OUT, update);
4562
client.off(client.Event.SDK_UPDATE, update);
46-
47-
setStateFactory(factory);
4863
}
4964

5065
if (updateOnSdkReady) {
@@ -61,10 +76,7 @@ export function SplitFactoryProvider(props: ISplitFactoryProps) {
6176
}
6277
if (updateOnSdkUpdate) client.on(client.Event.SDK_UPDATE, update);
6378

64-
return () => {
65-
// Factory destroy unsubscribes from events
66-
destroySplitFactory(factory as IFactoryWithClients);
67-
}
79+
return unsubscribe;
6880
}
6981
}, [config, updateOnSdkReady, updateOnSdkReadyFromCache, updateOnSdkTimedout, updateOnSdkUpdate]);
7082

src/__tests__/SplitFactoryProvider.test.tsx

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -336,39 +336,43 @@ describe('SplitFactoryProvider', () => {
336336
logSpy.mockRestore();
337337
});
338338

339-
test('cleans up on update and unmount.', () => {
339+
test('cleans up on update and unmount if config prop is provided.', () => {
340340
let renderTimes = 0;
341341
const createdFactories = new Set<SplitIO.ISDK>();
342342
const clientDestroySpies: jest.SpyInstance[] = [];
343+
const outerFactory = SplitSdk(sdkBrowser);
343344

344345
const Component = ({ factory, isReady, hasTimedout }: ISplitFactoryChildProps) => {
345346
renderTimes++;
346-
if (factory) createdFactories.add(factory);
347347

348348
switch (renderTimes) {
349349
case 1:
350-
case 3:
350+
expect(factory).toBe(outerFactory);
351+
return null;
352+
case 2:
353+
case 5:
351354
expect(isReady).toBe(false);
352355
expect(hasTimedout).toBe(false);
353356
expect(factory).toBe(null);
354357
return null;
355-
356-
case 2:
358+
case 3:
357359
case 4:
360+
case 6:
358361
expect(isReady).toBe(true);
359362
expect(hasTimedout).toBe(true);
360-
expect(__factories.size).toBe(1);
361-
clientDestroySpies.push(jest.spyOn((factory as SplitIO.ISDK).client(), 'destroy'));
363+
expect(factory).not.toBe(null);
364+
createdFactories.add(factory!);
365+
clientDestroySpies.push(jest.spyOn(factory!.client(), 'destroy'));
362366
return (
363367
<SplitClient splitKey='other_key' >
364368
{({ client }) => {
365-
clientDestroySpies.push(jest.spyOn(client as SplitIO.IClient, 'destroy'));
369+
clientDestroySpies.push(jest.spyOn(client!, 'destroy'));
366370
return null;
367371
}}
368372
</SplitClient>
369373
);
370-
case 5:
371-
throw new Error('Child must not be rerendered');
374+
case 7:
375+
throw new Error('Must not rerender');
372376
}
373377
};
374378

@@ -378,24 +382,41 @@ describe('SplitFactoryProvider', () => {
378382
factory.client().__emitter__.emit(Event.SDK_READY)
379383
};
380384

381-
// 1st render
385+
// 1st render: factory provided
382386
const wrapper = render(
387+
<SplitFactoryProvider factory={outerFactory} >
388+
{Component}
389+
</SplitFactoryProvider>
390+
);
391+
392+
// 2nd render: factory created, not ready (null)
393+
wrapper.rerender(
383394
<SplitFactoryProvider config={sdkBrowser} >
384395
{Component}
385396
</SplitFactoryProvider>
386397
);
387398

388-
// 2nd render: SDK ready (timeout is ignored due to updateOnSdkTimedout=false)
399+
// 3rd render: SDK ready (timeout is ignored due to updateOnSdkTimedout=false)
389400
act(emitSdkEvents);
390401

391-
// 3rd render: Update config prop -> factory is recreated
402+
// 4th render: same config prop -> factory is not recreated
403+
wrapper.rerender(
404+
<SplitFactoryProvider config={sdkBrowser} updateOnSdkReady={false} updateOnSdkTimedout={true} >
405+
{Component}
406+
</SplitFactoryProvider>
407+
);
408+
409+
act(emitSdkEvents); // Emitting events again has no effect
410+
expect(createdFactories.size).toBe(1);
411+
412+
// 5th render: Update config prop -> factory is recreated, not ready yet (null)
392413
wrapper.rerender(
393414
<SplitFactoryProvider config={{ ...sdkBrowser }} updateOnSdkReady={false} updateOnSdkTimedout={true} >
394415
{Component}
395416
</SplitFactoryProvider>
396417
);
397418

398-
// 4th render: SDK timeout (ready is ignored due to updateOnSdkReady=false)
419+
// 6th render: SDK timeout (ready is ignored due to updateOnSdkReady=false)
399420
act(emitSdkEvents);
400421

401422
wrapper.unmount();
@@ -415,11 +436,11 @@ describe('SplitFactoryProvider', () => {
415436
{({ factory }) => {
416437
// if factory is provided as a prop, `factories` cache is not modified
417438
expect(__factories.size).toBe(0);
418-
destroyMainClientSpy = jest.spyOn((factory as SplitIO.ISDK).client(), 'destroy');
439+
destroyMainClientSpy = jest.spyOn(factory!.client(), 'destroy');
419440
return (
420441
<SplitClient splitKey='other_key' >
421442
{({ client }) => {
422-
destroySharedClientSpy = jest.spyOn(client as SplitIO.IClient, 'destroy');
443+
destroySharedClientSpy = jest.spyOn(client!, 'destroy');
423444
return null;
424445
}}
425446
</SplitClient>

0 commit comments

Comments
 (0)