From 13cd5a2d20f950ec19ed6152ca43b31ee401bad8 Mon Sep 17 00:00:00 2001 From: Sophie Alpert Date: Wed, 26 Sep 2018 12:48:33 -0700 Subject: [PATCH] [private] Disallow useContext in class components --- .../src/ReactFiberDispatcher.js | 34 ++++++++++--------- .../react-reconciler/src/ReactFiberHooks.js | 12 +++++++ .../ReactNewContext-test.internal.js | 19 +++++++++-- packages/react/src/React.js | 33 +++++++++--------- packages/react/src/ReactHooks.js | 9 +++++ 5 files changed, 72 insertions(+), 35 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberDispatcher.js b/packages/react-reconciler/src/ReactFiberDispatcher.js index cae3a2c4af..10c0dee649 100644 --- a/packages/react-reconciler/src/ReactFiberDispatcher.js +++ b/packages/react-reconciler/src/ReactFiberDispatcher.js @@ -9,26 +9,28 @@ import {readContext} from './ReactFiberNewContext'; import { - useState, - useReducer, - useEffect, - useMutationEffect, - useLayoutEffect, - useCallback, - useMemo, - useRef, useAPI, + useCallback, + useContext, + useEffect, + useLayoutEffect, + useMemo, + useMutationEffect, + useReducer, + useRef, + useState, } from './ReactFiberHooks'; export const Dispatcher = { readContext, - useState, - useReducer, - useEffect, - useMutationEffect, - useLayoutEffect, - useCallback, - useMemo, - useRef, useAPI, + useCallback, + useContext, + useEffect, + useLayoutEffect, + useMemo, + useMutationEffect, + useReducer, + useRef, + useState, }; diff --git a/packages/react-reconciler/src/ReactFiberHooks.js b/packages/react-reconciler/src/ReactFiberHooks.js index 8b2aa2e7e9..8d61bcf127 100644 --- a/packages/react-reconciler/src/ReactFiberHooks.js +++ b/packages/react-reconciler/src/ReactFiberHooks.js @@ -7,11 +7,13 @@ * @flow */ +import type {ReactContext} from 'shared/ReactTypes'; import type {Fiber} from './ReactFiber'; import type {ExpirationTime} from './ReactFiberExpirationTime'; import type {HookEffectTag} from './ReactHookEffectTags'; import {NoWork} from './ReactFiberExpirationTime'; +import {readContext} from './ReactFiberNewContext'; import { Snapshot as SnapshotEffect, Update as UpdateEffect, @@ -318,6 +320,16 @@ function basicStateReducer(state: S, action: BasicStateAction): S { return typeof action === 'function' ? action(state) : action; } +export function useContext( + context: ReactContext, + observedBits: void | number | boolean, +): T { + // Ensure we're in a functional component (class components support only the + // .unstable_read() form) + resolveCurrentlyRenderingFiber(); + return readContext(context, observedBits); +} + export function useState( initialState: S | (() => S), ): [S, Dispatch>] { diff --git a/packages/react-reconciler/src/__tests__/ReactNewContext-test.internal.js b/packages/react-reconciler/src/__tests__/ReactNewContext-test.internal.js index a565ac8608..6455993f07 100644 --- a/packages/react-reconciler/src/__tests__/ReactNewContext-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactNewContext-test.internal.js @@ -56,12 +56,12 @@ describe('ReactNewContext', () => { }, ); sharedContextTests( - 'useContext inside class component', + 'unstable_read inside class component', Context => class Consumer extends React.Component { render() { const observedBits = this.props.unstable_observedBits; - const contextValue = useContext(Context, observedBits); + const contextValue = Context.unstable_read(observedBits); const render = this.props.children; return render(contextValue); } @@ -1195,7 +1195,7 @@ describe('ReactNewContext', () => { return ( {foo => { - const bar = useContext(BarContext); + const bar = BarContext.unstable_read(); return ; }} @@ -1330,6 +1330,19 @@ describe('ReactNewContext', () => { span('Baz: 2'), ]); }); + + it('throws when used in a class component', () => { + const Context = React.createContext(0); + class Foo extends React.Component { + render() { + return useContext(Context); + } + } + ReactNoop.render(); + expect(ReactNoop.flush).toThrow( + 'Hooks can only be called inside the body of a functional component.', + ); + }); }); it('unwinds after errors in complete phase', () => { diff --git a/packages/react/src/React.js b/packages/react/src/React.js index a6c7d20b4d..d03078ed16 100644 --- a/packages/react/src/React.js +++ b/packages/react/src/React.js @@ -24,20 +24,21 @@ import { cloneElement, isValidElement, } from './ReactElement'; -import {readContext, createContext} from './ReactContext'; +import {createContext} from './ReactContext'; import {lazy} from './ReactLazy'; import forwardRef from './forwardRef'; import pure from './pure'; import { - useState, + useAPI, + useCallback, + useContext, + useEffect, + useLayoutEffect, + useMemo, + useMutationEffect, useReducer, useRef, - useEffect, - useMutationEffect, - useLayoutEffect, - useCallback, - useMemo, - useAPI, + useState, } from './ReactHooks'; import { createElementWithValidation, @@ -84,16 +85,16 @@ if (enableSuspense) { } if (enableHooks_DEPRECATED) { - React.useContext = readContext; - React.useState = useState; + React.useAPI = useAPI; + React.useCallback = useCallback; + React.useContext = useContext; + React.useEffect = useEffect; + React.useLayoutEffect = useLayoutEffect; + React.useMemo = useMemo; + React.useMutationEffect = useMutationEffect; React.useReducer = useReducer; React.useRef = useRef; - React.useEffect = useEffect; - React.useMutationEffect = useMutationEffect; - React.useLayoutEffect = useLayoutEffect; - React.useCallback = useCallback; - React.useMemo = useMemo; - React.useAPI = useAPI; + React.useState = useState; } export default React; diff --git a/packages/react/src/ReactHooks.js b/packages/react/src/ReactHooks.js index 1ca4a7043d..406fe42f7a 100644 --- a/packages/react/src/ReactHooks.js +++ b/packages/react/src/ReactHooks.js @@ -7,6 +7,7 @@ * @flow */ +import type {ReactContext} from 'shared/ReactTypes'; import invariant from 'shared/invariant'; import ReactCurrentOwner from './ReactCurrentOwner'; @@ -20,6 +21,14 @@ function resolveDispatcher() { return dispatcher; } +export function useContext( + context: ReactContext, + observedBits: void | number | boolean, +): T { + const dispatcher = resolveDispatcher(); + return dispatcher.useContext(context, observedBits); +} + export function useState(initialState: S | (() => S)) { const dispatcher = resolveDispatcher(); return dispatcher.useState(initialState);