Skip to content

Commit 3546050

Browse files
Merge pull request #173 from splitio/development
Release v1.10.1
2 parents d7ae0d5 + 74dea45 commit 3546050

File tree

6 files changed

+86
-42
lines changed

6 files changed

+86
-42
lines changed

CHANGES.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
1.10.1 (November 21, 2023)
2+
- Bugfixing - Resolved an issue with `useSplitClient` hook, which was not re-rendering when an SDK client event was emitted between the hook's call (render phase) and the execution of its internal effect (commit phase).
3+
14
1.10.0 (November 16, 2023)
25
- Added support for Flag Sets on the SDK, which enables grouping feature flags and interacting with the group rather than individually (more details in our documentation):
36
- Added a new `flagSets` prop to the `SplitTreatments` component and `flagSets` option to the `useSplitTreatments` hook options object, to support evaluating flags in given flag set/s. Either `names` or `flagSets` must be provided to the component and hook. If both are provided, `names` will be used.

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@splitsoftware/splitio-react",
3-
"version": "1.10.0",
3+
"version": "1.10.1",
44
"description": "A React library to easily integrate and use Split JS SDK",
55
"main": "lib/index.js",
66
"module": "es/index.js",

src/SplitClient.tsx

Lines changed: 22 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -63,37 +63,36 @@ export class SplitComponent extends React.Component<IUpdateProps & { factory: Sp
6363
// The listeners take into account the value of `updateOnSdk***` props.
6464
subscribeToEvents(client: SplitIO.IBrowserClient | null) {
6565
if (client) {
66-
const status = getStatus(client);
67-
if (!status.isReady) client.once(client.Event.SDK_READY, this.setReady);
68-
if (!status.isReadyFromCache) client.once(client.Event.SDK_READY_FROM_CACHE, this.setReadyFromCache);
69-
if (!status.hasTimedout && !status.isReady) client.once(client.Event.SDK_READY_TIMED_OUT, this.setTimedout);
70-
client.on(client.Event.SDK_UPDATE, this.setUpdate);
66+
const statusOnEffect = getStatus(client);
67+
const status = this.state;
68+
69+
if (this.props.updateOnSdkReady) {
70+
if (!statusOnEffect.isReady) client.once(client.Event.SDK_READY, this.update);
71+
else if (!status.isReady) this.update();
72+
}
73+
if (this.props.updateOnSdkReadyFromCache) {
74+
if (!statusOnEffect.isReadyFromCache) client.once(client.Event.SDK_READY_FROM_CACHE, this.update);
75+
else if (!status.isReadyFromCache) this.update();
76+
}
77+
if (this.props.updateOnSdkTimedout) {
78+
if (!statusOnEffect.hasTimedout) client.once(client.Event.SDK_READY_TIMED_OUT, this.update);
79+
else if (!status.hasTimedout) this.update();
80+
}
81+
if (this.props.updateOnSdkUpdate) client.on(client.Event.SDK_UPDATE, this.update);
7182
}
7283
}
7384

7485
unsubscribeFromEvents(client: SplitIO.IBrowserClient | null) {
7586
if (client) {
76-
client.removeListener(client.Event.SDK_READY, this.setReady);
77-
client.removeListener(client.Event.SDK_READY_FROM_CACHE, this.setReadyFromCache);
78-
client.removeListener(client.Event.SDK_READY_TIMED_OUT, this.setTimedout);
79-
client.removeListener(client.Event.SDK_UPDATE, this.setUpdate);
87+
client.off(client.Event.SDK_READY, this.update);
88+
client.off(client.Event.SDK_READY_FROM_CACHE, this.update);
89+
client.off(client.Event.SDK_READY_TIMED_OUT, this.update);
90+
client.off(client.Event.SDK_UPDATE, this.update);
8091
}
8192
}
8293

83-
setReady = () => {
84-
if (this.props.updateOnSdkReady) this.setState({ lastUpdate: (this.state.client as IClientWithContext).lastUpdate });
85-
}
86-
87-
setReadyFromCache = () => {
88-
if (this.props.updateOnSdkReadyFromCache) this.setState({ lastUpdate: (this.state.client as IClientWithContext).lastUpdate });
89-
}
90-
91-
setTimedout = () => {
92-
if (this.props.updateOnSdkTimedout) this.setState({ lastUpdate: (this.state.client as IClientWithContext).lastUpdate });
93-
}
94-
95-
setUpdate = () => {
96-
if (this.props.updateOnSdkUpdate) this.setState({ lastUpdate: (this.state.client as IClientWithContext).lastUpdate });
94+
update = () => {
95+
this.setState({ lastUpdate: (this.state.client as IClientWithContext).lastUpdate });
9796
}
9897

9998
componentDidMount() {

src/__tests__/useSplitClient.test.tsx

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ describe('useSplitClient', () => {
8282

8383
// eslint-disable-next-line react/prop-types
8484
const InnerComponent = ({ splitKey, attributesClient, testSwitch }) => {
85-
useSplitClient({ splitKey, trafficType: 'user', attributes: attributesClient});
85+
useSplitClient({ splitKey, trafficType: 'user', attributes: attributesClient });
8686
testSwitch(done, splitKey);
8787
return null;
8888
};
@@ -98,13 +98,13 @@ describe('useSplitClient', () => {
9898
testAttributesBinding(Component);
9999
});
100100

101-
test('useSplitClient must update on SDK events', () => {
101+
test('must update on SDK events', () => {
102102
const outerFactory = SplitSdk(sdkBrowser);
103103
const mainClient = outerFactory.client() as any;
104104
const user2Client = outerFactory.client('user_2') as any;
105105

106106
let countSplitContext = 0, countSplitClient = 0, countSplitClientUser2 = 0, countUseSplitClient = 0, countUseSplitClientUser2 = 0;
107-
let countSplitClientWithUpdate = 0, countUseSplitClientWithUpdate = 0, countSplitClientUser2WithUpdate = 0, countUseSplitClientUser2WithUpdate = 0;
107+
let countSplitClientWithUpdate = 0, countUseSplitClientWithUpdate = 0, countSplitClientUser2WithUpdate = 0, countUseSplitClientUser2WithTimeout = 0;
108108
let countNestedComponent = 0;
109109

110110
render(
@@ -150,8 +150,8 @@ describe('useSplitClient', () => {
150150
{() => { countSplitClientUser2WithUpdate++; return null }}
151151
</SplitClient>
152152
{React.createElement(() => {
153-
useSplitClient({ splitKey: 'user_2', updateOnSdkUpdate: true });
154-
countUseSplitClientUser2WithUpdate++;
153+
useSplitClient({ splitKey: 'user_2', updateOnSdkTimedout: true });
154+
countUseSplitClientUser2WithTimeout++;
155155
return null;
156156
})}
157157
<SplitClient splitKey={'user_2'} updateOnSdkUpdate={true}>
@@ -190,6 +190,7 @@ describe('useSplitClient', () => {
190190
act(() => mainClient.__emitter__.emit(Event.SDK_READY));
191191
act(() => mainClient.__emitter__.emit(Event.SDK_UPDATE));
192192
act(() => user2Client.__emitter__.emit(Event.SDK_READY_FROM_CACHE));
193+
act(() => user2Client.__emitter__.emit(Event.SDK_READY_TIMED_OUT));
193194
act(() => user2Client.__emitter__.emit(Event.SDK_READY));
194195
act(() => user2Client.__emitter__.emit(Event.SDK_UPDATE));
195196

@@ -214,12 +215,35 @@ describe('useSplitClient', () => {
214215
// If SplitClient and useSplitClient retrieve a different client than the context and have updateOnSdkUpdate = true,
215216
// they render when the context renders and when the new client is ready, ready from cache and updates.
216217
expect(countSplitClientUser2WithUpdate).toEqual(countSplitContext + 3);
217-
expect(countUseSplitClientUser2WithUpdate).toEqual(countSplitContext + 3);
218+
expect(countUseSplitClientUser2WithTimeout).toEqual(countSplitContext + 3);
218219

219220
expect(countNestedComponent).toEqual(4);
220221
});
221222

222-
test('useSplitClient must support changes in update props', () => {
223+
// Remove this test once side effects are moved to the useSplitClient effect.
224+
test('must update on SDK events between the render phase (hook call) and commit phase (effect call)', () =>{
225+
const outerFactory = SplitSdk(sdkBrowser);
226+
let count = 0;
227+
228+
render(
229+
<SplitFactory factory={outerFactory} >
230+
{React.createElement(() => {
231+
useSplitClient({ splitKey: 'some_user' });
232+
count++;
233+
234+
// side effect in the render phase
235+
const client = outerFactory.client('some_user') as any;
236+
if (!client.__getStatus().isReady) client.__emitter__.emit(Event.SDK_READY);
237+
238+
return null;
239+
})}
240+
</SplitFactory>
241+
)
242+
243+
expect(count).toEqual(2);
244+
});
245+
246+
test('must support changes in update props', () => {
223247
const outerFactory = SplitSdk(sdkBrowser);
224248
const mainClient = outerFactory.client() as any;
225249

@@ -240,19 +264,24 @@ describe('useSplitClient', () => {
240264
}
241265

242266
const wrapper = render(<Component />);
267+
expect(rendersCount).toBe(1);
243268

244269
act(() => mainClient.__emitter__.emit(Event.SDK_READY)); // trigger re-render
270+
expect(rendersCount).toBe(2);
271+
245272
act(() => mainClient.__emitter__.emit(Event.SDK_UPDATE)); // do not trigger re-render because updateOnSdkUpdate is false by default
246273
expect(rendersCount).toBe(2);
247274

248275
wrapper.rerender(<Component updateOnSdkUpdate={true} />); // trigger re-render
249-
act(() => mainClient.__emitter__.emit(Event.SDK_UPDATE)); // trigger re-render because updateOnSdkUpdate is true now
276+
expect(rendersCount).toBe(3);
250277

278+
act(() => mainClient.__emitter__.emit(Event.SDK_UPDATE)); // trigger re-render because updateOnSdkUpdate is true now
251279
expect(rendersCount).toBe(4);
252280

253281
wrapper.rerender(<Component updateOnSdkUpdate={false} />); // trigger re-render
254-
act(() => mainClient.__emitter__.emit(Event.SDK_UPDATE)); // do not trigger re-render because updateOnSdkUpdate is false now
282+
expect(rendersCount).toBe(5);
255283

284+
act(() => mainClient.__emitter__.emit(Event.SDK_UPDATE)); // do not trigger re-render because updateOnSdkUpdate is false now
256285
expect(rendersCount).toBe(5);
257286
});
258287

src/useSplitClient.ts

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,23 +33,36 @@ export function useSplitClient(options?: IUseSplitClientOptions): ISplitContextV
3333

3434
let client = contextClient as IClientWithContext;
3535
if (splitKey && factory) {
36+
// @TODO `getSplitClient` starts client sync. Move side effects to useEffect
3637
client = getSplitClient(factory, splitKey, trafficType);
3738
}
3839
initAttributes(client, attributes);
3940

40-
const [, setLastUpdate] = React.useState(client ? client.lastUpdate : 0);
41+
const status = getStatus(client);
42+
const [, setLastUpdate] = React.useState(status.lastUpdate);
4143

4244
// Handle client events
4345
React.useEffect(() => {
4446
if (!client) return;
4547

4648
const update = () => setLastUpdate(client.lastUpdate);
4749

50+
// Clients are created on the hook's call, so the status may have changed
51+
const statusOnEffect = getStatus(client);
52+
4853
// Subscribe to SDK events
49-
const status = getStatus(client);
50-
if (!status.isReady && updateOnSdkReady) client.once(client.Event.SDK_READY, update);
51-
if (!status.isReadyFromCache && updateOnSdkReadyFromCache) client.once(client.Event.SDK_READY_FROM_CACHE, update);
52-
if (!status.hasTimedout && !status.isReady && updateOnSdkTimedout) client.once(client.Event.SDK_READY_TIMED_OUT, update);
54+
if (updateOnSdkReady) {
55+
if (!statusOnEffect.isReady) client.once(client.Event.SDK_READY, update);
56+
else if (!status.isReady) update();
57+
}
58+
if (updateOnSdkReadyFromCache) {
59+
if (!statusOnEffect.isReadyFromCache) client.once(client.Event.SDK_READY_FROM_CACHE, update);
60+
else if (!status.isReadyFromCache) update();
61+
}
62+
if (updateOnSdkTimedout) {
63+
if (!statusOnEffect.hasTimedout) client.once(client.Event.SDK_READY_TIMED_OUT, update);
64+
else if (!status.hasTimedout) update();
65+
}
5366
if (updateOnSdkUpdate) client.on(client.Event.SDK_UPDATE, update);
5467

5568
return () => {
@@ -59,9 +72,9 @@ export function useSplitClient(options?: IUseSplitClientOptions): ISplitContextV
5972
client.off(client.Event.SDK_READY_TIMED_OUT, update);
6073
client.off(client.Event.SDK_UPDATE, update);
6174
}
62-
}, [client, updateOnSdkReady, updateOnSdkReadyFromCache, updateOnSdkTimedout, updateOnSdkUpdate]);
75+
}, [client, updateOnSdkReady, updateOnSdkReadyFromCache, updateOnSdkTimedout, updateOnSdkUpdate, status]);
6376

6477
return {
65-
factory, client, ...getStatus(client)
78+
factory, client, ...status
6679
};
6780
}

0 commit comments

Comments
 (0)