Skip to content

Commit c927044

Browse files
authored
fix: failure to re-render on changes (#1021)
## This PR - maintains a reference to the latest evaluation details. ### Notes The change to optimize the rendering behavior was comparing a stale value. #987 This PR adds a [mutable ref](https://reactjs.org/docs/hooks-faq.html#is-there-something-like-instance-variables) that's updated every time the evaluation details are updated. ### Follow-up Tasks This was manually tested. Unit tests will follow. Signed-off-by: Michael Beemer <[email protected]>
1 parent baec2fb commit c927044

File tree

1 file changed

+15
-9
lines changed

1 file changed

+15
-9
lines changed

packages/react/src/evaluation/use-feature-flag.ts

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
ProviderEvents,
88
ProviderStatus,
99
} from '@openfeature/web-sdk';
10-
import { useEffect, useState } from 'react';
10+
import { useEffect, useRef, useState } from 'react';
1111
import { DEFAULT_OPTIONS, ReactFlagEvaluationOptions, normalizeOptions } from '../common/options';
1212
import { suspendUntilReady } from '../common/suspense';
1313
import { useProviderOptions } from '../provider/context';
@@ -290,44 +290,50 @@ function attachHandlersAndResolve<T extends FlagValue>(
290290
resolver(client).call(client, flagKey, defaultValue, options),
291291
);
292292

293-
const updateEvaluationDetailsRef = () => {
293+
// Maintain a mutable reference to the evaluation details to have a up-to-date reference in the handlers.
294+
const evaluationDetailsRef = useRef<EvaluationDetails<T>>(evaluationDetails);
295+
useEffect(() => {
296+
evaluationDetailsRef.current = evaluationDetails;
297+
}, [evaluationDetails]);
298+
299+
const updateEvaluationDetailsCallback = () => {
294300
const updatedEvaluationDetails = resolver(client).call(client, flagKey, defaultValue, options);
295301

296302
/**
297303
* Avoid re-rendering if the value hasn't changed. We could expose a means
298304
* to define a custom comparison function if users require a more
299305
* sophisticated comparison in the future.
300306
*/
301-
if (!isEqual(updatedEvaluationDetails.value, evaluationDetails.value)) {
307+
if (!isEqual(updatedEvaluationDetails.value, evaluationDetailsRef.current.value)) {
302308
setEvaluationDetails(updatedEvaluationDetails);
303309
}
304310
};
305311

306312
useEffect(() => {
307313
if (status === ProviderStatus.NOT_READY) {
308314
// update when the provider is ready
309-
client.addHandler(ProviderEvents.Ready, updateEvaluationDetailsRef);
315+
client.addHandler(ProviderEvents.Ready, updateEvaluationDetailsCallback);
310316
}
311317

312318
if (defaultedOptions.updateOnContextChanged) {
313319
// update when the context changes
314-
client.addHandler(ProviderEvents.ContextChanged, updateEvaluationDetailsRef);
320+
client.addHandler(ProviderEvents.ContextChanged, updateEvaluationDetailsCallback);
315321
}
316322
return () => {
317323
// cleanup the handlers
318-
client.removeHandler(ProviderEvents.Ready, updateEvaluationDetailsRef);
319-
client.removeHandler(ProviderEvents.ContextChanged, updateEvaluationDetailsRef);
324+
client.removeHandler(ProviderEvents.Ready, updateEvaluationDetailsCallback);
325+
client.removeHandler(ProviderEvents.ContextChanged, updateEvaluationDetailsCallback);
320326
};
321327
}, []);
322328

323329
useEffect(() => {
324330
if (defaultedOptions.updateOnConfigurationChanged) {
325331
// update when the provider configuration changes
326-
client.addHandler(ProviderEvents.ConfigurationChanged, updateEvaluationDetailsRef);
332+
client.addHandler(ProviderEvents.ConfigurationChanged, updateEvaluationDetailsCallback);
327333
}
328334
return () => {
329335
// cleanup the handlers
330-
client.removeHandler(ProviderEvents.ConfigurationChanged, updateEvaluationDetailsRef);
336+
client.removeHandler(ProviderEvents.ConfigurationChanged, updateEvaluationDetailsCallback);
331337
};
332338
}, []);
333339

0 commit comments

Comments
 (0)