Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Experiment] Context Selectors #20646

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Move API to unstable useContext option
This will to make it easier to A/B test, or to revert if we abandon the
experiment. Using a selector will not change the return type of
`useContext`. Use a userspace hook to get the selected value:

```js
function useContextSelector<C, S>(Context: C, selector: C => S): S {
  const context = useContext(Context, {unstable_selector: selector});
  const selectedContext = selector(context);
  return selectedContext;
}
```
  • Loading branch information
acdlite committed Jul 10, 2021
commit 85dc024a1ab7555241e13a4b9b2baae44e17328b
18 changes: 4 additions & 14 deletions packages/react-debug-tools/src/ReactDebugHooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,10 @@ function readContext<T>(context: ReactContext<T>): T {
return context._currentValue;
}

function useContext<T>(context: ReactContext<T>): T {
function useContext<T, S>(
context: ReactContext<T>,
options?: {unstable_selector?: T => S},
): T {
hookLog.push({
primitive: 'Context',
stackError: new Error(),
Expand All @@ -123,18 +126,6 @@ function useContext<T>(context: ReactContext<T>): T {
return context._currentValue;
}

function useContextSelector<C, S>(
context: ReactContext<C>,
selector: C => S,
): C {
hookLog.push({
primitive: 'ContextSelector',
stackError: new Error(),
value: context._currentValue,
});
return context._currentValue;
}

function useState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
Expand Down Expand Up @@ -328,7 +319,6 @@ const Dispatcher: DispatcherType = {
useCacheRefresh,
useCallback,
useContext,
useContextSelector,
useEffect,
useImperativeHandle,
useDebugValue,
Expand Down
19 changes: 4 additions & 15 deletions packages/react-dom/src/server/ReactPartialRendererHooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,10 @@ function readContext<T>(context: ReactContext<T>): T {
return context[threadID];
}

function useContext<T>(context: ReactContext<T>): T {
function useContext<T, S>(
context: ReactContext<T>,
options?: {unstable_selector?: T => S},
): T {
if (__DEV__) {
currentHookNameInDev = 'useContext';
}
Expand All @@ -245,19 +248,6 @@ function useContext<T>(context: ReactContext<T>): T {
return context[threadID];
}

function useContextSelector<C, S>(
context: ReactContext<C>,
selector: C => S,
): C {
if (__DEV__) {
currentHookNameInDev = 'useContextSelector';
}
resolveCurrentlyRenderingComponent();
const threadID = currentPartialRenderer.threadID;
validateContextBounds(context, threadID);
return context[threadID];
}

function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
// $FlowFixMe: Flow doesn't like mixed types
return typeof action === 'function' ? action(state) : action;
Expand Down Expand Up @@ -510,7 +500,6 @@ export function setCurrentPartialRenderer(renderer: PartialRenderer) {
export const Dispatcher: DispatcherType = {
readContext,
useContext,
useContextSelector,
useMemo,
useReducer,
useRef,
Expand Down
152 changes: 51 additions & 101 deletions packages/react-reconciler/src/ReactFiberHooks.new.js
Original file line number Diff line number Diff line change
Expand Up @@ -680,6 +680,19 @@ function updateWorkInProgressHook(): Hook {
return workInProgressHook;
}

function useContext<T, S>(
context: ReactContext<T>,
options?: {unstable_selector?: T => S},
): T {
if (options !== undefined) {
const selector = options.unstable_selector;
if (selector !== undefined) {
return readContextWithSelector(context, selector);
}
}
return readContext(context);
}

function createFunctionComponentUpdateQueue(): FunctionComponentUpdateQueue {
return {
lastEffect: null,
Expand Down Expand Up @@ -2071,7 +2084,6 @@ export const ContextOnlyDispatcher: Dispatcher = {

useCallback: throwInvalidHookError,
useContext: throwInvalidHookError,
useContextSelector: throwInvalidHookError,
useEffect: throwInvalidHookError,
useImperativeHandle: throwInvalidHookError,
useLayoutEffect: throwInvalidHookError,
Expand All @@ -2096,8 +2108,7 @@ const HooksDispatcherOnMount: Dispatcher = {
readContext,

useCallback: mountCallback,
useContext: readContext,
useContextSelector: readContextWithSelector,
useContext: useContext,
useEffect: mountEffect,
useImperativeHandle: mountImperativeHandle,
useLayoutEffect: mountLayoutEffect,
Expand All @@ -2122,8 +2133,7 @@ const HooksDispatcherOnUpdate: Dispatcher = {
readContext,

useCallback: updateCallback,
useContext: readContext,
useContextSelector: readContextWithSelector,
useContext: useContext,
useEffect: updateEffect,
useImperativeHandle: updateImperativeHandle,
useLayoutEffect: updateLayoutEffect,
Expand All @@ -2148,8 +2158,7 @@ const HooksDispatcherOnRerender: Dispatcher = {
readContext,

useCallback: updateCallback,
useContext: readContext,
useContextSelector: readContextWithSelector,
useContext: useContext,
useEffect: updateEffect,
useImperativeHandle: updateImperativeHandle,
useLayoutEffect: updateLayoutEffect,
Expand Down Expand Up @@ -2207,21 +2216,13 @@ if (__DEV__) {
checkDepsAreArrayDev(deps);
return mountCallback(callback, deps);
},
useContext<T>(context: ReactContext<T>): T {
useContext<T, S>(
context: ReactContext<T>,
options?: {unstable_selector?: T => S},
): T {
currentHookNameInDev = 'useContext';
mountHookTypesDev();
return readContext(context);
},
useContextSelector<C, S>(context: ReactContext<C>, selector: C => S): C {
currentHookNameInDev = 'useContextSelector';
mountHookTypesDev();
const prevDispatcher = ReactCurrentDispatcher.current;
ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnMountInDEV;
try {
return readContextWithSelector(context, selector);
} finally {
ReactCurrentDispatcher.current = prevDispatcher;
}
return useContext(context, options);
},
useEffect(
create: () => (() => void) | void,
Expand Down Expand Up @@ -2346,21 +2347,13 @@ if (__DEV__) {
updateHookTypesDev();
return mountCallback(callback, deps);
},
useContext<T>(context: ReactContext<T>): T {
useContext<T, S>(
context: ReactContext<T>,
options?: {unstable_selector?: T => S},
): T {
currentHookNameInDev = 'useContext';
updateHookTypesDev();
return readContext(context);
},
useContextSelector<C, S>(context: ReactContext<C>, selector: C => S): C {
currentHookNameInDev = 'useContextSelector';
updateHookTypesDev();
const prevDispatcher = ReactCurrentDispatcher.current;
ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnMountInDEV;
try {
return readContextWithSelector(context, selector);
} finally {
ReactCurrentDispatcher.current = prevDispatcher;
}
return useContext(context, options);
},
useEffect(
create: () => (() => void) | void,
Expand Down Expand Up @@ -2481,21 +2474,13 @@ if (__DEV__) {
updateHookTypesDev();
return updateCallback(callback, deps);
},
useContext<T>(context: ReactContext<T>): T {
useContext<T, S>(
context: ReactContext<T>,
options?: {unstable_selector?: T => S},
): T {
currentHookNameInDev = 'useContext';
updateHookTypesDev();
return readContext(context);
},
useContextSelector<C, S>(context: ReactContext<C>, selector: C => S): C {
currentHookNameInDev = 'useContextSelector';
updateHookTypesDev();
const prevDispatcher = ReactCurrentDispatcher.current;
ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
try {
return readContextWithSelector(context, selector);
} finally {
ReactCurrentDispatcher.current = prevDispatcher;
}
return useContext(context, options);
},
useEffect(
create: () => (() => void) | void,
Expand Down Expand Up @@ -2617,21 +2602,13 @@ if (__DEV__) {
updateHookTypesDev();
return updateCallback(callback, deps);
},
useContext<T>(context: ReactContext<T>): T {
useContext<T, S>(
context: ReactContext<T>,
options?: {unstable_selector?: T => S},
): T {
currentHookNameInDev = 'useContext';
updateHookTypesDev();
return readContext(context);
},
useContextSelector<C, S>(context: ReactContext<C>, selector: C => S): C {
currentHookNameInDev = 'useContextSelector';
updateHookTypesDev();
const prevDispatcher = ReactCurrentDispatcher.current;
ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnRerenderInDEV;
try {
return readContextWithSelector(context, selector);
} finally {
ReactCurrentDispatcher.current = prevDispatcher;
}
return useContext(context, options);
},
useEffect(
create: () => (() => void) | void,
Expand Down Expand Up @@ -2754,23 +2731,14 @@ if (__DEV__) {
mountHookTypesDev();
return mountCallback(callback, deps);
},
useContext<T>(context: ReactContext<T>): T {
useContext<T, S>(
context: ReactContext<T>,
options?: {unstable_selector?: T => S},
): T {
currentHookNameInDev = 'useContext';
warnInvalidHookAccess();
mountHookTypesDev();
return readContext(context);
},
useContextSelector<C, S>(context: ReactContext<C>, selector: C => S): C {
currentHookNameInDev = 'useContextSelector';
warnInvalidHookAccess();
mountHookTypesDev();
const prevDispatcher = ReactCurrentDispatcher.current;
ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnMountInDEV;
try {
return readContextWithSelector(context, selector);
} finally {
ReactCurrentDispatcher.current = prevDispatcher;
}
return useContext(context, options);
},
useEffect(
create: () => (() => void) | void,
Expand Down Expand Up @@ -2905,23 +2873,14 @@ if (__DEV__) {
updateHookTypesDev();
return updateCallback(callback, deps);
},
useContext<T>(context: ReactContext<T>): T {
useContext<T, S>(
context: ReactContext<T>,
options?: {unstable_selector?: T => S},
): T {
currentHookNameInDev = 'useContext';
warnInvalidHookAccess();
updateHookTypesDev();
return readContext(context);
},
useContextSelector<C, S>(context: ReactContext<C>, selector: C => S): C {
currentHookNameInDev = 'useContextSelector';
warnInvalidHookAccess();
updateHookTypesDev();
const prevDispatcher = ReactCurrentDispatcher.current;
ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
try {
return readContextWithSelector(context, selector);
} finally {
ReactCurrentDispatcher.current = prevDispatcher;
}
return useContext(context, options);
},
useEffect(
create: () => (() => void) | void,
Expand Down Expand Up @@ -3057,23 +3016,14 @@ if (__DEV__) {
updateHookTypesDev();
return updateCallback(callback, deps);
},
useContext<T>(context: ReactContext<T>): T {
useContext<T, S>(
context: ReactContext<T>,
options?: {unstable_selector?: T => S},
): T {
currentHookNameInDev = 'useContext';
warnInvalidHookAccess();
updateHookTypesDev();
return readContext(context);
},
useContextSelector<C, S>(context: ReactContext<C>, selector: C => S): C {
currentHookNameInDev = 'useContextSelector';
warnInvalidHookAccess();
updateHookTypesDev();
const prevDispatcher = ReactCurrentDispatcher.current;
ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
try {
return readContextWithSelector(context, selector);
} finally {
ReactCurrentDispatcher.current = prevDispatcher;
}
return useContext(context, options);
},
useEffect(
create: () => (() => void) | void,
Expand Down
Loading