[Hooks] Remove dispatch callbacks (#14037)

Removes the `enableDispatchCallback` feature flag and deletes the
associated code. An earlier version of the Hooks proposal included this
feature but we've since decided to remove it.
This commit is contained in:
Andrew Clark
2018-10-30 14:14:20 -07:00
committed by GitHub
parent 8c67bbf183
commit bf9fadfcf4
13 changed files with 33 additions and 215 deletions
+16 -20
View File
@@ -12,22 +12,21 @@ import invariant from 'shared/invariant';
import warning from 'shared/warning';
type BasicStateAction<S> = (S => S) | S;
type MaybeCallback<S> = void | null | (S => mixed);
type Dispatch<S, A> = (A, MaybeCallback<S>) => void;
type Dispatch<A> = A => void;
type Update<S, A> = {
type Update<A> = {
action: A,
next: Update<S, A> | null,
next: Update<A> | null,
};
type UpdateQueue<S, A> = {
last: Update<S, A> | null,
type UpdateQueue<A> = {
last: Update<A> | null,
dispatch: any,
};
type Hook = {
memoizedState: any,
queue: UpdateQueue<any, any> | null,
queue: UpdateQueue<any> | null,
next: Hook | null,
};
@@ -39,10 +38,7 @@ let isReRender: boolean = false;
// Whether an update was scheduled during the currently executing render pass.
let didScheduleRenderPhaseUpdate: boolean = false;
// Lazily created map of render-phase updates
let renderPhaseUpdates: Map<
UpdateQueue<any, any>,
Update<any, any>,
> | null = null;
let renderPhaseUpdates: Map<UpdateQueue<any>, Update<any>> | null = null;
// Counter to prevent infinite loops.
let numberOfReRenders: number = 0;
const RE_RENDER_LIMIT = 25;
@@ -159,7 +155,7 @@ function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
export function useState<S>(
initialState: (() => S) | S,
): [S, Dispatch<S, BasicStateAction<S>>] {
): [S, Dispatch<BasicStateAction<S>>] {
return useReducer(
basicStateReducer,
// useReducer has a special case to support lazy useState initializers
@@ -171,14 +167,14 @@ export function useReducer<S, A>(
reducer: (S, A) => S,
initialState: S,
initialAction: A | void | null,
): [S, Dispatch<S, A>] {
): [S, Dispatch<A>] {
currentlyRenderingComponent = resolveCurrentlyRenderingComponent();
workInProgressHook = createWorkInProgressHook();
if (isReRender) {
// This is a re-render. Apply the new render phase updates to the previous
// current hook.
const queue: UpdateQueue<S, A> = (workInProgressHook.queue: any);
const dispatch: Dispatch<S, A> = (queue.dispatch: any);
const queue: UpdateQueue<A> = (workInProgressHook.queue: any);
const dispatch: Dispatch<A> = (queue.dispatch: any);
if (renderPhaseUpdates !== null) {
// Render phase updates are stored in a map of queue -> linked list
const firstRenderPhaseUpdate = renderPhaseUpdates.get(queue);
@@ -211,11 +207,11 @@ export function useReducer<S, A>(
initialState = reducer(initialState, initialAction);
}
workInProgressHook.memoizedState = initialState;
const queue: UpdateQueue<S, A> = (workInProgressHook.queue = {
const queue: UpdateQueue<A> = (workInProgressHook.queue = {
last: null,
dispatch: null,
});
const dispatch: Dispatch<S, A> = (queue.dispatch = (dispatchAction.bind(
const dispatch: Dispatch<A> = (queue.dispatch = (dispatchAction.bind(
null,
currentlyRenderingComponent,
queue,
@@ -294,9 +290,9 @@ export function useLayoutEffect(
);
}
function dispatchAction<S, A>(
function dispatchAction<A>(
componentIdentity: Object,
queue: UpdateQueue<S, A>,
queue: UpdateQueue<A>,
action: A,
) {
invariant(
@@ -310,7 +306,7 @@ function dispatchAction<S, A>(
// queue -> linked list of updates. After this render pass, we'll restart
// and apply the stashed updates on top of the work-in-progress hook.
didScheduleRenderPhaseUpdate = true;
const update: Update<S, A> = {
const update: Update<A> = {
action,
next: null,
};
-15
View File
@@ -319,21 +319,6 @@ function commitLifeCycles(
case ForwardRef:
case SimpleMemoComponent: {
commitHookEffectList(UnmountLayout, MountLayout, finishedWork);
const newUpdateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
if (newUpdateQueue !== null) {
const callbackList = newUpdateQueue.callbackList;
if (callbackList !== null) {
newUpdateQueue.callbackList = null;
for (let i = 0; i < callbackList.length; i++) {
const update = callbackList[i];
// Assume this is non-null, since otherwise it would not be part
// of the callback list.
const callback: () => mixed = (update.callback: any);
update.callback = null;
callback();
}
}
}
break;
}
case ClassComponent: {
+17 -72
View File
@@ -17,7 +17,6 @@ import {readContext} from './ReactFiberNewContext';
import {
Snapshot as SnapshotEffect,
Update as UpdateEffect,
Callback as CallbackEffect,
Passive as PassiveEffect,
} from 'shared/ReactSideEffectTags';
import {
@@ -38,18 +37,15 @@ import {
import invariant from 'shared/invariant';
import warning from 'shared/warning';
import warningWithoutStack from 'shared/warningWithoutStack';
import {enableDispatchCallback_DEPRECATED} from 'shared/ReactFeatureFlags';
type Update<S, A> = {
type Update<A> = {
expirationTime: ExpirationTime,
action: A,
callback: null | (S => mixed),
next: Update<S, A> | null,
next: Update<A> | null,
};
type UpdateQueue<S, A> = {
last: Update<S, A> | null,
type UpdateQueue<A> = {
last: Update<A> | null,
dispatch: any,
};
@@ -57,8 +53,8 @@ type Hook = {
memoizedState: any,
baseState: any,
baseUpdate: Update<any, any> | null,
queue: UpdateQueue<any, any> | null,
baseUpdate: Update<any> | null,
queue: UpdateQueue<any> | null,
next: Hook | null,
};
@@ -72,15 +68,12 @@ type Effect = {
};
export type FunctionComponentUpdateQueue = {
callbackList: Array<Update<any, any>> | null,
lastEffect: Effect | null,
};
type BasicStateAction<S> = (S => S) | S;
type MaybeCallback<S> = void | null | (S => mixed);
type Dispatch<S, A> = (A, MaybeCallback<S>) => void;
type Dispatch<A> = A => void;
// These are set right before calling the component.
let renderExpirationTime: ExpirationTime = NoWork;
@@ -113,10 +106,7 @@ let isReRender: boolean = false;
// Whether an update was scheduled during the currently executing render pass.
let didScheduleRenderPhaseUpdate: boolean = false;
// Lazily created map of render-phase updates
let renderPhaseUpdates: Map<
UpdateQueue<any, any>,
Update<any, any>,
> | null = null;
let renderPhaseUpdates: Map<UpdateQueue<any>, Update<any>> | null = null;
// Counter to prevent infinite loops.
let numberOfReRenders: number = 0;
const RE_RENDER_LIMIT = 25;
@@ -313,7 +303,6 @@ function createWorkInProgressHook(): Hook {
function createFunctionComponentUpdateQueue(): FunctionComponentUpdateQueue {
return {
callbackList: null,
lastEffect: null,
};
}
@@ -334,7 +323,7 @@ export function useContext<T>(
export function useState<S>(
initialState: (() => S) | S,
): [S, Dispatch<S, BasicStateAction<S>>] {
): [S, Dispatch<BasicStateAction<S>>] {
return useReducer(
basicStateReducer,
// useReducer has a special case to support lazy useState initializers
@@ -346,16 +335,16 @@ export function useReducer<S, A>(
reducer: (S, A) => S,
initialState: S,
initialAction: A | void | null,
): [S, Dispatch<S, A>] {
): [S, Dispatch<A>] {
currentlyRenderingFiber = resolveCurrentlyRenderingFiber();
workInProgressHook = createWorkInProgressHook();
let queue: UpdateQueue<S, A> | null = (workInProgressHook.queue: any);
let queue: UpdateQueue<A> | null = (workInProgressHook.queue: any);
if (queue !== null) {
// Already have a queue, so this is an update.
if (isReRender) {
// This is a re-render. Apply the new render phase updates to the previous
// work-in-progress hook.
const dispatch: Dispatch<S, A> = (queue.dispatch: any);
const dispatch: Dispatch<A> = (queue.dispatch: any);
if (renderPhaseUpdates !== null) {
// Render phase updates are stored in a map of queue -> linked list
const firstRenderPhaseUpdate = renderPhaseUpdates.get(queue);
@@ -369,10 +358,6 @@ export function useReducer<S, A>(
// render's.
const action = update.action;
newState = reducer(newState, action);
const callback = update.callback;
if (callback !== null) {
pushCallback(currentlyRenderingFiber, update);
}
update = update.next;
} while (update !== null);
@@ -439,10 +424,6 @@ export function useReducer<S, A>(
// Process this update.
const action = update.action;
newState = reducer(newState, action);
const callback = update.callback;
if (callback !== null) {
pushCallback(currentlyRenderingFiber, update);
}
}
prevUpdate = update;
update = update.next;
@@ -458,7 +439,7 @@ export function useReducer<S, A>(
workInProgressHook.baseState = newBaseState;
}
const dispatch: Dispatch<S, A> = (queue.dispatch: any);
const dispatch: Dispatch<A> = (queue.dispatch: any);
return [workInProgressHook.memoizedState, dispatch];
}
@@ -476,7 +457,7 @@ export function useReducer<S, A>(
last: null,
dispatch: null,
};
const dispatch: Dispatch<S, A> = (queue.dispatch = (dispatchAction.bind(
const dispatch: Dispatch<A> = (queue.dispatch = (dispatchAction.bind(
null,
currentlyRenderingFiber,
queue,
@@ -484,21 +465,6 @@ export function useReducer<S, A>(
return [workInProgressHook.memoizedState, dispatch];
}
function pushCallback(workInProgress: Fiber, update: Update<any, any>): void {
if (componentUpdateQueue === null) {
componentUpdateQueue = createFunctionComponentUpdateQueue();
componentUpdateQueue.callbackList = [update];
} else {
const callbackList = componentUpdateQueue.callbackList;
if (callbackList === null) {
componentUpdateQueue.callbackList = [update];
} else {
callbackList.push(update);
}
}
workInProgress.effectTag |= CallbackEffect;
}
function pushEffect(tag, create, destroy, inputs) {
const effect: Effect = {
tag,
@@ -677,26 +643,7 @@ export function useMemo<T>(
return nextValue;
}
function dispatchAction<S, A>(
fiber: Fiber,
queue: UpdateQueue<S, A>,
action: A,
callback: void | null | (S => mixed),
) {
if (enableDispatchCallback_DEPRECATED) {
if (__DEV__) {
if (typeof callback === 'function') {
warningWithoutStack(
false,
'Update callbacks (the second argument to dispatch/setState) are ' +
'deprecated. Try useEffect instead.',
);
}
}
} else {
callback = null;
}
function dispatchAction<A>(fiber: Fiber, queue: UpdateQueue<A>, action: A) {
invariant(
numberOfReRenders < RE_RENDER_LIMIT,
'Too many re-renders. React limits the number of renders to prevent ' +
@@ -712,10 +659,9 @@ function dispatchAction<S, A>(
// queue -> linked list of updates. After this render pass, we'll restart
// and apply the stashed updates on top of the work-in-progress hook.
didScheduleRenderPhaseUpdate = true;
const update: Update<S, A> = {
const update: Update<A> = {
expirationTime: renderExpirationTime,
action,
callback: callback !== undefined ? callback : null,
next: null,
};
if (renderPhaseUpdates === null) {
@@ -735,10 +681,9 @@ function dispatchAction<S, A>(
} else {
const currentTime = requestCurrentTime();
const expirationTime = computeExpirationForFiber(currentTime, fiber);
const update: Update<S, A> = {
const update: Update<A> = {
expirationTime,
action,
callback: callback !== undefined ? callback : null,
next: null,
};
flushPassiveEffects();
@@ -61,7 +61,6 @@ describe('ReactHooksWithNoopRenderer', () => {
ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
ReactFeatureFlags.enableHooks = true;
ReactFeatureFlags.enableDispatchCallback_DEPRECATED = true;
ReactFeatureFlags.enableSchedulerTracing = true;
React = require('react');
ReactNoop = require('react-noop-renderer');
@@ -233,104 +232,6 @@ describe('ReactHooksWithNoopRenderer', () => {
expect(ReactNoop.flush()).toEqual(['Total: 7']);
});
it('callbacks', () => {
function Counter(props, ref) {
const [count, updateCount] = useState(0);
useImperativeMethods(ref, () => ({updateCount}));
return <Text text={'Count: ' + count} />;
}
Counter = forwardRef(Counter);
const counter = React.createRef(null);
ReactNoop.render(<Counter ref={counter} />);
ReactNoop.flush();
expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);
expect(() => {
counter.current.updateCount(7, count => {
ReactNoop.yield(`Did update count`);
});
}).toWarnDev(
'Warning: Update callbacks (the second argument to ' +
'dispatch/setState) are deprecated. Try useEffect instead.',
{withoutStack: true},
);
expect(ReactNoop.flush()).toEqual(['Count: 7', 'Did update count']);
// Update twice in the same batch
expect(() => {
counter.current.updateCount(1, () => {
ReactNoop.yield(`Did update count (first callback)`);
});
}).toWarnDev(
'Warning: Update callbacks (the second argument to ' +
'dispatch/setState) are deprecated. Try useEffect instead.',
{withoutStack: true},
);
expect(() => {
counter.current.updateCount(2, () => {
ReactNoop.yield(`Did update count (second callback)`);
});
}).toWarnDev(
'Warning: Update callbacks (the second argument to ' +
'dispatch/setState) are deprecated. Try useEffect instead.',
{withoutStack: true},
);
expect(ReactNoop.flush()).toEqual([
// Component only renders once
'Count: 2',
'Did update count (first callback)',
'Did update count (second callback)',
]);
});
it('does not fire callbacks more than once when rebasing', () => {
function Counter(props, ref) {
const [count, updateCount] = useState(0);
useImperativeMethods(ref, () => ({updateCount}));
return <Text text={'Count: ' + count} />;
}
Counter = forwardRef(Counter);
const counter = React.createRef(null);
ReactNoop.render(<Counter ref={counter} />);
ReactNoop.flush();
expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);
expect(() => {
counter.current.updateCount(1, count => {
ReactNoop.yield(`Did update count (low pri)`);
});
}).toWarnDev(
'Warning: Update callbacks (the second argument to ' +
'dispatch/setState) are deprecated. Try useEffect instead.',
{withoutStack: true},
);
ReactNoop.flushSync(() => {
expect(() => {
counter.current.updateCount(2, count => {
ReactNoop.yield(`Did update count (high pri)`);
});
}).toWarnDev(
'Warning: Update callbacks (the second argument to ' +
'dispatch/setState) are deprecated. Try useEffect instead.',
{withoutStack: true},
);
});
expect(ReactNoop.clearYields()).toEqual([
'Count: 2',
'Did update count (high pri)',
]);
// The high-pri update is processed again when we render at low priority,
// but its callback should not fire again.
expect(ReactNoop.flush()).toEqual([
'Count: 2',
'Did update count (low pri)',
]);
});
it('returns the same updater function every time', () => {
let updaters = [];
function Counter() {
-1
View File
@@ -10,7 +10,6 @@
export const enableUserTimingAPI = __DEV__;
export const enableHooks = false;
export const enableDispatchCallback_DEPRECATED = false;
// Helps identify side effects in begin-phase lifecycle hooks and setState reducers:
export const debugRenderPhaseSideEffects = false;
@@ -16,7 +16,6 @@ export const debugRenderPhaseSideEffects = false;
export const debugRenderPhaseSideEffectsForStrictMode = false;
export const enableUserTimingAPI = __DEV__;
export const enableHooks = false;
export const enableDispatchCallback_DEPRECATED = false;
export const warnAboutDeprecatedLifecycles = false;
export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__;
export const enableProfilerTimer = __PROFILE__;
@@ -16,7 +16,6 @@ export const debugRenderPhaseSideEffects = false;
export const debugRenderPhaseSideEffectsForStrictMode = false;
export const enableUserTimingAPI = __DEV__;
export const enableHooks = false;
export const enableDispatchCallback_DEPRECATED = false;
export const warnAboutDeprecatedLifecycles = false;
export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__;
export const enableProfilerTimer = __PROFILE__;
@@ -15,7 +15,6 @@ import typeof * as FeatureFlagsShimType from './ReactFeatureFlags.native-fb';
// Re-export dynamic flags from the fbsource version.
export const {
enableHooks,
enableDispatchCallback_DEPRECATED,
debugRenderPhaseSideEffects,
debugRenderPhaseSideEffectsForStrictMode,
warnAboutDeprecatedLifecycles,
@@ -15,7 +15,6 @@ import typeof * as FeatureFlagsShimType from './ReactFeatureFlags.native-oss';
export const debugRenderPhaseSideEffects = false;
export const debugRenderPhaseSideEffectsForStrictMode = false;
export const enableHooks = false;
export const enableDispatchCallback_DEPRECATED = false;
export const enableUserTimingAPI = __DEV__;
export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__;
export const warnAboutDeprecatedLifecycles = false;
@@ -16,7 +16,6 @@ export const debugRenderPhaseSideEffects = false;
export const debugRenderPhaseSideEffectsForStrictMode = false;
export const enableUserTimingAPI = __DEV__;
export const enableHooks = false;
export const enableDispatchCallback_DEPRECATED = false;
export const warnAboutDeprecatedLifecycles = false;
export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__;
export const enableProfilerTimer = __PROFILE__;
@@ -16,7 +16,6 @@ export const debugRenderPhaseSideEffects = false;
export const debugRenderPhaseSideEffectsForStrictMode = false;
export const enableUserTimingAPI = __DEV__;
export const enableHooks = false;
export const enableDispatchCallback_DEPRECATED = false;
export const warnAboutDeprecatedLifecycles = false;
export const replayFailedUnitOfWorkWithInvokeGuardedCallback = false;
export const enableProfilerTimer = false;
@@ -16,7 +16,6 @@ export const debugRenderPhaseSideEffects = false;
export const debugRenderPhaseSideEffectsForStrictMode = false;
export const enableUserTimingAPI = __DEV__;
export const enableHooks = false;
export const enableDispatchCallback_DEPRECATED = false;
export const warnAboutDeprecatedLifecycles = false;
export const replayFailedUnitOfWorkWithInvokeGuardedCallback = false;
export const enableProfilerTimer = false;
@@ -18,7 +18,6 @@ export const {
replayFailedUnitOfWorkWithInvokeGuardedCallback,
warnAboutDeprecatedLifecycles,
disableInputAttributeSyncing,
enableDispatchCallback_DEPRECATED,
} = require('ReactFeatureFlags');
// The rest of the flags are static for better dead code elimination.