Skip to content

Commit e95d8d6

Browse files
fix(#129): detect variant payload change in useVariant hook (#132)
As described in #129, the react SDK didn't take into account if a variant's payload had changed when getting updates, resulting in potentially stale data. This PR addresses that by checking all parts of the variant before determining whether it has changed. Closes #129
1 parent 1a7ed5a commit e95d8d6

File tree

2 files changed

+99
-5
lines changed

2 files changed

+99
-5
lines changed

src/useVariant.test.tsx

+85-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { vi } from 'vitest';
22
import { renderHook } from '@testing-library/react-hooks/native';
33
import { useContext } from 'react';
4-
import useVariant from './useVariant';
4+
import useVariant, { variantHasChanged } from './useVariant';
55

66
vi.mock('react', async () => {
77
const react = (await vi.importActual('react')) as any;
@@ -158,3 +158,87 @@ test('should remove event listeners when unmounted', () => {
158158
expect(clientMock.off).nthCalledWith(1, ...clientMock.on.mock.calls[0]);
159159
expect(clientMock.off).nthCalledWith(2, ...clientMock.on.mock.calls[1]);
160160
});
161+
162+
describe('Variant change detection', () => {
163+
test('If the variants are identical, it returns `false`', () => {
164+
const a = {
165+
name: 'a',
166+
enabled: true,
167+
payload: {
168+
type: 'string',
169+
value: 'data',
170+
},
171+
};
172+
const b = {
173+
name: 'a',
174+
enabled: true,
175+
payload: {
176+
type: 'string',
177+
value: 'data',
178+
},
179+
};
180+
181+
expect(variantHasChanged(a, b)).toBeFalsy();
182+
});
183+
184+
test('If the new variant is undefined, it counts as a change', () => {
185+
const a = { name: 'a', enabled: true };
186+
187+
expect(variantHasChanged(a, undefined)).toBeTruthy();
188+
});
189+
190+
test('Name change is detected', () => {
191+
const a = { name: 'a', enabled: true };
192+
const b = { name: 'b', enabled: true };
193+
194+
expect(variantHasChanged(a, b)).toBeTruthy();
195+
});
196+
197+
test('Enabled state change is detected', () => {
198+
const enabled = { name: 'a', enabled: true };
199+
const disabled = { name: 'a', enabled: false };
200+
201+
expect(variantHasChanged(enabled, disabled)).toBeTruthy();
202+
});
203+
test('Payload type change is detected', () => {
204+
const a = {
205+
name: 'a',
206+
enabled: true,
207+
payload: {
208+
type: 'string',
209+
value: '{}',
210+
},
211+
};
212+
const b = {
213+
name: 'a',
214+
enabled: true,
215+
payload: {
216+
type: 'json',
217+
value: '{}',
218+
},
219+
};
220+
221+
expect(variantHasChanged(a, b)).toBeTruthy();
222+
});
223+
224+
test('Payload value change is detected', () => {
225+
const a = {
226+
name: 'a',
227+
enabled: true,
228+
payload: {
229+
type: 'string',
230+
value: '1',
231+
},
232+
};
233+
const b = {
234+
name: 'a',
235+
enabled: true,
236+
payload: {
237+
type: 'string',
238+
value: '2',
239+
},
240+
};
241+
242+
expect(variantHasChanged(a, b)).toBeTruthy();
243+
});
244+
});

src/useVariant.ts

+14-4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,19 @@ import { useContext, useState, useEffect, useRef } from 'react';
22
import { IVariant } from 'unleash-proxy-client';
33
import FlagContext from './FlagContext';
44

5+
export const variantHasChanged = (
6+
oldVariant: IVariant,
7+
newVariant?: IVariant,
8+
): boolean => {
9+
const variantsAreEqual =
10+
oldVariant.name === newVariant?.name &&
11+
oldVariant.enabled === newVariant?.enabled &&
12+
oldVariant.payload?.type === newVariant?.payload?.type &&
13+
oldVariant.payload?.value === newVariant?.payload?.value;
14+
15+
return !variantsAreEqual;
16+
};
17+
518
const useVariant = (name: string): Partial<IVariant> => {
619
const { getVariant, client } = useContext(FlagContext);
720

@@ -17,10 +30,7 @@ const useVariant = (name: string): Partial<IVariant> => {
1730

1831
const updateHandler = () => {
1932
const newVariant = getVariant(name);
20-
if (
21-
variantRef.current.name !== newVariant?.name ||
22-
variantRef.current.enabled !== newVariant?.enabled
23-
) {
33+
if (variantHasChanged(variantRef.current, newVariant)) {
2434
setVariant(newVariant);
2535
variantRef.current = newVariant;
2636
}

0 commit comments

Comments
 (0)