Skip to content

Commit 4182e5a

Browse files
committed
fix: useContextSelector with React 18
1 parent 39be388 commit 4182e5a

File tree

1 file changed

+30
-54
lines changed

1 file changed

+30
-54
lines changed

packages/react-components/react-context-selector/src/useContextSelector.ts

Lines changed: 30 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,6 @@ import * as React from 'react';
33

44
import { Context, ContextSelector, ContextValue, ContextVersion } from './types';
55

6-
/**
7-
* Narrowing React.Reducer type to be more easily usable below.
8-
* No need to export this as it's for internal reducer usage.
9-
*/
10-
type ContextReducer<Value, SelectedValue> = React.Reducer<
11-
readonly [Value, SelectedValue],
12-
undefined | readonly [ContextVersion, Value]
13-
>;
14-
156
/**
167
* @internal
178
* This hook returns context selected value by selector.
@@ -31,49 +22,51 @@ export const useContextSelector = <Value, SelectedValue>(
3122
} = contextValue;
3223
const selected = selector(value);
3324

34-
const [state, dispatch] = React.useReducer<ContextReducer<Value, SelectedValue>>(
25+
const [state, setState] = React.useState<readonly [Value, SelectedValue]>([value, selected]);
26+
const dispatch = React.useCallback(
3527
(
36-
prevState: readonly [Value /* contextValue */, SelectedValue /* selector(value) */],
3728
payload:
3829
| undefined // undefined from render below
3930
| readonly [ContextVersion, Value], // from provider effect
40-
): readonly [Value, SelectedValue] => {
41-
if (!payload) {
42-
// early bail out when is dispatched during render
43-
return [value, selected] as const;
44-
}
45-
46-
if (payload[0] <= version) {
47-
if (objectIs(prevState[1], selected)) {
48-
return prevState; // bail out
31+
) => {
32+
setState(prevState => {
33+
if (!payload) {
34+
// early bail out when is dispatched during render
35+
return [value, selected] as const;
4936
}
5037

51-
return [value, selected] as const;
52-
}
38+
if (payload[0] <= version) {
39+
if (Object.is(prevState[1], selected)) {
40+
return prevState; // bail out
41+
}
5342

54-
try {
55-
if (objectIs(prevState[0], payload[1])) {
56-
return prevState; // do not update
43+
return [value, selected] as const;
5744
}
5845

59-
const nextSelected = selector(payload[1]);
46+
try {
47+
if (Object.is(prevState[0], payload[1])) {
48+
return prevState; // do not update
49+
}
6050

61-
if (objectIs(prevState[1], nextSelected)) {
62-
return prevState; // do not update
63-
}
51+
const nextSelected = selector(payload[1]);
52+
53+
if (Object.is(prevState[1], nextSelected)) {
54+
return prevState; // do not update
55+
}
6456

65-
return [payload[1], nextSelected] as const;
66-
} catch (e) {
67-
// ignored (stale props or some other reason)
68-
}
57+
return [payload[1], nextSelected] as const;
58+
} catch (e) {
59+
// ignored (stale props or some other reason)
60+
}
6961

70-
// explicitly spread to enforce typing
71-
return [prevState[0], prevState[1]] as const; // schedule update
62+
// explicitly spread to enforce typing
63+
return [prevState[0], prevState[1]] as const; // schedule update
64+
});
7265
},
73-
[value, selected] as const,
66+
[],
7467
);
7568

76-
if (!objectIs(state[1], selected)) {
69+
if (!Object.is(state[1], selected)) {
7770
// schedule re-render
7871
// this is safe because it's self contained
7972
dispatch(undefined);
@@ -90,20 +83,3 @@ export const useContextSelector = <Value, SelectedValue>(
9083

9184
return state[1] as SelectedValue;
9285
};
93-
94-
/**
95-
* inlined Object.is polyfill to avoid requiring consumers ship their own
96-
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
97-
*/
98-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
99-
function is(x: any, y: any) {
100-
return (
101-
(x === y && (x !== 0 || 1 / x === 1 / y)) || (x !== x && y !== y) // eslint-disable-line no-self-compare
102-
);
103-
}
104-
105-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
106-
const objectIs: (x: any, y: any) => boolean =
107-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
108-
// @ts-ignore fallback to native if it exists (not in IE11)
109-
typeof Object.is === 'function' ? Object.is : is;

0 commit comments

Comments
 (0)