Skip to content

Commit

Permalink
fix: useContextSelector with React 18 (microsoft#30951)
Browse files Browse the repository at this point in the history
  • Loading branch information
layershifter authored Jun 13, 2024
1 parent 01e9388 commit 9494a77
Show file tree
Hide file tree
Showing 2 changed files with 26 additions and 44 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "fix: useContextSelector with React 18",
"packageName": "@fluentui/react-context-selector",
"email": "olfedias@microsoft.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,8 @@
import { useIsomorphicLayoutEffect } from '@fluentui/react-utilities';
import { useEventCallback, useIsomorphicLayoutEffect } from '@fluentui/react-utilities';
import * as React from 'react';

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

/**
* Narrowing React.Reducer type to be more easily usable below.
* No need to export this as it's for internal reducer usage.
*/
type ContextReducer<Value, SelectedValue> = React.Reducer<
readonly [Value, SelectedValue],
undefined | readonly [ContextVersion, Value]
>;

/**
* @internal
* This hook returns context selected value by selector.
Expand All @@ -31,34 +22,34 @@ export const useContextSelector = <Value, SelectedValue>(
} = contextValue;
const selected = selector(value);

const [state, dispatch] = React.useReducer<ContextReducer<Value, SelectedValue>>(
(
prevState: readonly [Value /* contextValue */, SelectedValue /* selector(value) */],
payload:
| undefined // undefined from render below
| readonly [ContextVersion, Value], // from provider effect
): readonly [Value, SelectedValue] => {
const [state, setState] = React.useState<readonly [Value, SelectedValue]>([value, selected]);
const dispatch = (
payload:
| undefined // undefined from render below
| readonly [ContextVersion, Value], // from provider effect
) => {
setState(prevState => {
if (!payload) {
// early bail out when is dispatched during render
return [value, selected] as const;
}

if (payload[0] <= version) {
if (objectIs(prevState[1], selected)) {
if (Object.is(prevState[1], selected)) {
return prevState; // bail out
}

return [value, selected] as const;
}

try {
if (objectIs(prevState[0], payload[1])) {
if (Object.is(prevState[0], payload[1])) {
return prevState; // do not update
}

const nextSelected = selector(payload[1]);

if (objectIs(prevState[1], nextSelected)) {
if (Object.is(prevState[1], nextSelected)) {
return prevState; // do not update
}

Expand All @@ -69,41 +60,25 @@ export const useContextSelector = <Value, SelectedValue>(

// explicitly spread to enforce typing
return [prevState[0], prevState[1]] as const; // schedule update
},
[value, selected] as const,
);
});
};

if (!objectIs(state[1], selected)) {
if (!Object.is(state[1], selected)) {
// schedule re-render
// this is safe because it's self contained
dispatch(undefined);
}

const stableDispatch = useEventCallback(dispatch);

useIsomorphicLayoutEffect(() => {
listeners.push(dispatch);
listeners.push(stableDispatch);

return () => {
const index = listeners.indexOf(dispatch);
const index = listeners.indexOf(stableDispatch);
listeners.splice(index, 1);
};
}, [listeners]);
}, [stableDispatch, listeners]);

return state[1] as SelectedValue;
};

/**
* inlined Object.is polyfill to avoid requiring consumers ship their own
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function is(x: any, y: any) {
return (
(x === y && (x !== 0 || 1 / x === 1 / y)) || (x !== x && y !== y) // eslint-disable-line no-self-compare
);
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const objectIs: (x: any, y: any) => boolean =
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore fallback to native if it exists (not in IE11)
typeof Object.is === 'function' ? Object.is : is;

0 comments on commit 9494a77

Please sign in to comment.