149
import type {BatchConfigTransition} from './ReactFiberTracingMarkerComponent';
150
import {requestAsyncActionContext} from './ReactFiberAsyncAction';
151
import {HostTransitionContext} from './ReactFiberHostContext';
152
+import {requestTransitionLane} from './ReactFiberRootScheduler';
153
154
const {ReactCurrentDispatcher, ReactCurrentBatchConfig} = ReactSharedInternals;
155
156
export type Update<S, A> = {
157
lane: Lane,
158
+ revertLane: Lane,
159
action: A,
160
hasEagerState: boolean,
161
eagerState: S | null,
1138
init?: I => S,
1139
): [S, Dispatch<A>] {
1140
const hook = updateWorkInProgressHook();
1141
+ return updateReducerImpl(hook, ((currentHook: any): Hook), reducer);
1142
+}
1143
+
1144
+function updateReducerImpl<S, A>(
1145
+ hook: Hook,
1146
+ current: Hook,
1147
+ reducer: (S, A) => S,
1148
+): [S, Dispatch<A>] {
1149
const queue = hook.queue;
1150
1151
if (queue === null) {
1156
1157
queue.lastRenderedReducer = reducer;
1158
1149
- const current: Hook = (currentHook: any);
1150
-
1159
// The last rebase update that is NOT part of the base state.
1152
- let baseQueue = current.baseQueue;
1160
+ let baseQueue = hook.baseQueue;
1161
1162
// The last pending update that hasn't been processed yet.
1163
const pendingQueue = queue.pending;
1188
if (baseQueue !== null) {
1189
// We have a queue to process.
1190
const first = baseQueue.next;
1183
- let newState = current.baseState;
1191
+ let newState = hook.baseState;
1192
1193
let newBaseState = null;
1194
let newBaseQueueFirst = null;
1214
// update/state.
1215
const clone: Update<S, A> = {
1216
lane: updateLane,
1217
+ revertLane: update.revertLane,
1218
action: update.action,
1219
hasEagerState: update.hasEagerState,
1220
eagerState: update.eagerState,
1237
} else {
1238
// This update does have sufficient priority.
1239
1231
- if (newBaseQueueLast !== null) {
1232
- const clone: Update<S, A> = {
1233
- // This update is going to be committed so we never want uncommit
1234
- // it. Using NoLane works because 0 is a subset of all bitmasks, so
1235
- // this will never be skipped by the check above.
1236
- lane: NoLane,
1237
- action: update.action,
1238
- hasEagerState: update.hasEagerState,
1239
- eagerState: update.eagerState,
1240
- next: (null: any),
1241
- };
1242
- newBaseQueueLast = newBaseQueueLast.next = clone;
1240
+ // Check if this is an optimistic update.
1241
+ const revertLane = update.revertLane;
1242
+ if (!enableAsyncActions || revertLane === NoLane) {
1243
+ // This is not an optimistic update, and we're going to apply it now.
1244
+ // But, if there were earlier updates that were skipped, we need to
1245
+ // leave this update in the queue so it can be rebased later.
1246
+ if (newBaseQueueLast !== null) {
1247
+ const clone: Update<S, A> = {
1248
+ // This update is going to be committed so we never want uncommit
1249
+ // it. Using NoLane works because 0 is a subset of all bitmasks, so
1250
+ // this will never be skipped by the check above.
1251
+ lane: NoLane,
1252
+ revertLane: NoLane,
1253
+ action: update.action,
1254
+ hasEagerState: update.hasEagerState,
1255
+ eagerState: update.eagerState,
1256
+ next: (null: any),
1257
+ };
1258
+ newBaseQueueLast = newBaseQueueLast.next = clone;
1259
+ }
1260
+ } else {
1261
+ // This is an optimistic update. If the "revert" priority is
1262
+ // sufficient, don't apply the update. Otherwise, apply the update,
1263
+ // but leave it in the queue so it can be either reverted or
1264
+ // rebased in a subsequent render.
1265
+ if (isSubsetOfLanes(renderLanes, revertLane)) {
1266
+ // The transition that this optimistic update is associated with
1267
+ // has finished. Pretend the update doesn't exist by skipping
1268
+ // over it.
1269
+ update = update.next;
1270
+ continue;
1271
+ } else {
1272
+ const clone: Update<S, A> = {
1273
+ // Once we commit an optimistic update, we shouldn't uncommit it
1274
+ // until the transition it is associated with has finished
1275
+ // (represented by revertLane). Using NoLane here works because 0
1276
+ // is a subset of all bitmasks, so this will never be skipped by
1277
+ // the check above.
1278
+ lane: NoLane,
1279
+ // Reuse the same revertLane so we know when the transition
1280
+ // has finished.
1281
+ revertLane: update.revertLane,
1282
+ action: update.action,
1283
+ hasEagerState: update.hasEagerState,
1284
+ eagerState: update.eagerState,
1285
+ next: (null: any),
1286
+ };
1287
+ if (newBaseQueueLast === null) {
1288
+ newBaseQueueFirst = newBaseQueueLast = clone;
1289
+ newBaseState = newState;
1290
+ } else {
1291
+ newBaseQueueLast = newBaseQueueLast.next = clone;
1292
+ }
1293
+ // Update the remaining priority in the queue.
1294
+ // TODO: Don't need to accumulate this. Instead, we can remove
1295
+ // renderLanes from the original lanes.
1296
+ currentlyRenderingFiber.lanes = mergeLanes(
1297
+ currentlyRenderingFiber.lanes,
1298
+ revertLane,
1299
+ );
1300
+ markSkippedUpdateLanes(revertLane);
1301
+ }
1302
}
1303
1304
// Process this update.
1943
}
1944
}
1945
1887
-function mountState<S>(
1888
- initialState: (() => S) | S,
1889
-): [S, Dispatch<BasicStateAction<S>>] {
1946
+function mountStateImpl<S>(initialState: (() => S) | S): Hook {
1947
const hook = mountWorkInProgressHook();
1948
if (typeof initialState === 'function') {
1949
// $FlowFixMe[incompatible-use]: Flow doesn't like mixed types
1958
lastRenderedState: (initialState: any),
1959
};
1960
hook.queue = queue;
1904
- const dispatch: Dispatch<BasicStateAction<S>> = (queue.dispatch =
1905
- (dispatchSetState.bind(null, currentlyRenderingFiber, queue): any));
1961
+ return hook;
1962
+}
1963
+
1964
+function mountState<S>(
1965
+ initialState: (() => S) | S,
1966
+): [S, Dispatch<BasicStateAction<S>>] {
1967
+ const hook = mountStateImpl(initialState);
1968
+ const queue = hook.queue;
1969
+ const dispatch: Dispatch<BasicStateAction<S>> = (dispatchSetState.bind(
1970
+ null,
1971
+ currentlyRenderingFiber,
1972
+ queue,
1973
+ ): any);
1974
+ queue.dispatch = dispatch;
1975
return [hook.memoizedState, dispatch];
1976
}
1977
1978
function updateState<S>(
1979
initialState: (() => S) | S,
1980
): [S, Dispatch<BasicStateAction<S>>] {
1912
- return updateReducer(basicStateReducer, (initialState: any));
1981
+ return updateReducer(basicStateReducer, initialState);
1982
}
1983
1984
function rerenderState<S>(
1985
initialState: (() => S) | S,
1986
): [S, Dispatch<BasicStateAction<S>>] {
1918
- return rerenderReducer(basicStateReducer, (initialState: any));
1987
+ return rerenderReducer(basicStateReducer, initialState);
1988
+}
1989
+
1990
+function mountOptimisticState<S, A>(
1991
+ passthrough: S,
1992
+ reducer: ?(S, A) => S,
1993
+): [S, (A) => void] {
1994
+ const hook = mountWorkInProgressHook();
1995
+ hook.memoizedState = hook.baseState = passthrough;
1996
+ const queue: UpdateQueue<S, A> = {
1997
+ pending: null,
1998
+ lanes: NoLanes,
1999
+ dispatch: null,
2000
+ // Optimistic state does not use the eager update optimization.
2001
+ lastRenderedReducer: null,
2002
+ lastRenderedState: null,
2003
+ };
2004
+ hook.queue = queue;
2005
+ // This is different than the normal setState function.
2006
+ const dispatch: A => void = (dispatchOptimisticSetState.bind(
2007
+ null,
2008
+ currentlyRenderingFiber,
2009
+ true,
2010
+ queue,
2011
+ ): any);
2012
+ queue.dispatch = dispatch;
2013
+ return [passthrough, dispatch];
2014
+}
2015
+
2016
+function updateOptimisticState<S, A>(
2017
+ passthrough: S,
2018
+ reducer: ?(S, A) => S,
2019
+): [S, (A) => void] {
2020
+ const hook = updateWorkInProgressHook();
2021
+
2022
+ // Optimistic updates are always rebased on top of the latest value passed in
2023
+ // as an argument. It's called a passthrough because if there are no pending
2024
+ // updates, it will be returned as-is.
2025
+ //
2026
+ // Reset the base state and memoized state to the passthrough. Future
2027
+ // updates will be applied on top of this.
2028
+ hook.baseState = hook.memoizedState = passthrough;
2029
+
2030
+ // If a reducer is not provided, default to the same one used by useState.
2031
+ const resolvedReducer: (S, A) => S =
2032
+ typeof reducer === 'function' ? reducer : (basicStateReducer: any);
2033
+
2034
+ return updateReducerImpl(hook, ((currentHook: any): Hook), resolvedReducer);
2035
+}
2036
+
2037
+function rerenderOptimisticState<S, A>(
2038
+ passthrough: S,
2039
+ reducer: ?(S, A) => S,
2040
+): [S, (A) => void] {
2041
+ // Unlike useState, useOptimisticState doesn't support render phase updates.
2042
+ // Also unlike useState, we need to replay all pending updates again in case
2043
+ // the passthrough value changed.
2044
+ //
2045
+ // So instead of a forked re-render implementation that knows how to handle
2046
+ // render phase udpates, we can use the same implementation as during a
2047
+ // regular mount or update.
2048
+
2049
+ if (currentHook !== null) {
2050
+ // This is an update. Process the update queue.
2051
+ return updateOptimisticState(passthrough, reducer);
2052
+ }
2053
+
2054
+ // This is a mount. No updates to process.
2055
+ const hook = updateWorkInProgressHook();
2056
+ // Reset the base state and memoized state to the passthrough. Future
2057
+ // updates will be applied on top of this.
2058
+ hook.baseState = hook.memoizedState = passthrough;
2059
+ const dispatch = hook.queue.dispatch;
2060
+ return [passthrough, dispatch];
2061
}
2062
2063
function pushEffect(
2587
}
2588
2589
function startTransition<S>(
2590
+ fiber: Fiber,
2591
+ queue: UpdateQueue<S | Thenable<S>, BasicStateAction<S | Thenable<S>>>,
2592
pendingState: S,
2593
finishedState: S,
2450
- setPending: (Thenable<S> | S) => void,
2594
callback: () => mixed,
2595
options?: StartTransitionOptions,
2596
): void {
2600
);
2601
2602
const prevTransition = ReactCurrentBatchConfig.transition;
2460
- ReactCurrentBatchConfig.transition = null;
2461
- setPending(pendingState);
2603
+
2604
+ if (enableAsyncActions) {
2605
+ // We don't really need to use an optimistic update here, because we
2606
+ // schedule a second "revert" update below (which we use to suspend the
2607
+ // transition until the async action scope has finished). But we'll use an
2608
+ // optimistic update anyway to make it less likely the behavior accidentally
2609
+ // diverges; for example, both an optimistic update and this one should
2610
+ // share the same lane.
2611
+ dispatchOptimisticSetState(fiber, false, queue, pendingState);
2612
+ } else {
2613
+ ReactCurrentBatchConfig.transition = null;
2614
+ dispatchSetState(fiber, queue, pendingState);
2615
+ }
2616
+
2617
const currentTransition = (ReactCurrentBatchConfig.transition =
2618
({}: BatchConfigTransition));
2619
2640
returnValue,
2641
finishedState,
2642
);
2488
- setPending(maybeThenable);
2643
+ dispatchSetState(fiber, queue, maybeThenable);
2644
} else {
2645
// Async actions are not enabled.
2491
- setPending(finishedState);
2646
+ dispatchSetState(fiber, queue, finishedState);
2647
callback();
2648
}
2649
} catch (error) {
2656
status: 'rejected',
2657
reason: error,
2658
};
2504
- setPending(rejectedThenable);
2659
+ dispatchSetState(fiber, queue, rejectedThenable);
2660
} else {
2661
// The error rethrowing behavior is only enabled when the async actions
2662
// feature is on, even for sync actions.
2708
);
2709
}
2710
2556
- let setPending;
2711
+ let queue: UpdateQueue<
2712
+ Thenable<TransitionStatus> | TransitionStatus,
2713
+ BasicStateAction<Thenable<TransitionStatus> | TransitionStatus>,
2714
+ >;
2715
if (formFiber.memoizedState === null) {
2716
// Upgrade this host component fiber to be stateful. We're going to pretend
2717
// it was stateful all along so we can reuse most of the implementation
2719
//
2720
// Create the state hook used by TransitionAwareHostComponent. This is
2721
// essentially an inlined version of mountState.
2564
- const queue: UpdateQueue<
2565
- Thenable<TransitionStatus> | TransitionStatus,
2722
+ const newQueue: UpdateQueue<
2723
Thenable<TransitionStatus> | TransitionStatus,
2724
+ BasicStateAction<Thenable<TransitionStatus> | TransitionStatus>,
2725
> = {
2726
pending: null,
2727
lanes: NoLanes,
2570
- dispatch: null,
2728
+ // We're going to cheat and intentionally not create a bound dispatch
2729
+ // method, because we can call it directly in startTransition.
2730
+ dispatch: (null: any),
2731
lastRenderedReducer: basicStateReducer,
2732
lastRenderedState: NoPendingHostTransition,
2733
};
2734
+ queue = newQueue;
2735
+
2736
const stateHook: Hook = {
2737
memoizedState: NoPendingHostTransition,
2738
baseState: NoPendingHostTransition,
2739
baseQueue: null,
2578
- queue: queue,
2740
+ queue: newQueue,
2741
next: null,
2742
};
2743
2582
- const dispatch: (Thenable<TransitionStatus> | TransitionStatus) => void =
2583
- (dispatchSetState.bind(null, formFiber, queue): any);
2584
- setPending = queue.dispatch = dispatch;
2585
-
2744
// Add the state hook to both fiber alternates. The idea is that the fiber
2745
// had this hook all along.
2746
formFiber.memoizedState = stateHook;
2751
} else {
2752
// This fiber was already upgraded to be stateful.
2753
const stateHook: Hook = formFiber.memoizedState;
2596
- const dispatch: (Thenable<TransitionStatus> | TransitionStatus) => void =
2597
- stateHook.queue.dispatch;
2598
- setPending = dispatch;
2754
+ queue = stateHook.queue;
2755
}
2756
2757
startTransition(
2758
+ formFiber,
2759
+ queue,
2760
pendingState,
2761
NoPendingHostTransition,
2604
- setPending,
2762
// TODO: We can avoid this extra wrapper, somehow. Figure out layering
2763
// once more of this function is implemented.
2764
() => callback(formData),
2769
boolean,
2770
(callback: () => void, options?: StartTransitionOptions) => void,
2771
] {
2615
- const [, setPending] = mountState((false: Thenable<boolean> | boolean));
2772
+ const stateHook = mountStateImpl((false: Thenable<boolean> | boolean));
2773
// The `start` method never changes.
2617
- const start = startTransition.bind(null, true, false, setPending);
2774
+ const start = startTransition.bind(
2775
+ null,
2776
+ currentlyRenderingFiber,
2777
+ stateHook.queue,
2778
+ true,
2779
+ false,
2780
+ );
2781
const hook = mountWorkInProgressHook();
2782
hook.memoizedState = start;
2783
return [false, start];
2948
2949
const update: Update<S, A> = {
2950
lane,
2951
+ revertLane: NoLane,
2952
action,
2953
hasEagerState: false,
2954
eagerState: null,
2987
2988
const update: Update<S, A> = {
2989
lane,
2990
+ revertLane: NoLane,
2991
action,
2992
hasEagerState: false,
2993
eagerState: null,
3051
markUpdateInDevTools(fiber, lane, action);
3052
}
3053
3054
+function dispatchOptimisticSetState<S, A>(
3055
+ fiber: Fiber,
3056
+ throwIfDuringRender: boolean,
3057
+ queue: UpdateQueue<S, A>,
3058
+ action: A,
3059
+): void {
3060
+ const update: Update<S, A> = {
3061
+ // An optimistic update commits synchronously.
3062
+ lane: SyncLane,
3063
+ // After committing, the optimistic update is "reverted" using the same
3064
+ // lane as the transition it's associated with.
3065
+ //
3066
+ // TODO: Warn if there's no transition/action associated with this
3067
+ // optimistic update.
3068
+ revertLane: requestTransitionLane(),
3069
+ action,
3070
+ hasEagerState: false,
3071
+ eagerState: null,
3072
+ next: (null: any),
3073
+ };
3074
+
3075
+ if (isRenderPhaseUpdate(fiber)) {
3076
+ // When calling startTransition during render, this warns instead of
3077
+ // throwing because throwing would be a breaking change. setOptimisticState
3078
+ // is a new API so it's OK to throw.
3079
+ if (throwIfDuringRender) {
3080
+ throw new Error('Cannot update optimistic state while rendering.');
3081
+ } else {
3082
+ // startTransition was called during render. We don't need to do anything
3083
+ // besides warn here because the render phase update would be overidden by
3084
+ // the second update, anyway. We can remove this branch and make it throw
3085
+ // in a future release.
3086
+ if (__DEV__) {
3087
+ console.error('Cannot call startTransition while rendering.');
3088
+ }
3089
+ }
3090
+ } else {
3091
+ const root = enqueueConcurrentHookUpdate(fiber, queue, update, SyncLane);
3092
+ if (root !== null) {
3093
+ // NOTE: The optimistic update implementation assumes that the transition
3094
+ // will never be attempted before the optimistic update. This currently
3095
+ // holds because the optimistic update is always synchronous. If we ever
3096
+ // change that, we'll need to account for this.
3097
+ scheduleUpdateOnFiber(root, fiber, SyncLane);
3098
+ // Optimistic updates are always synchronous, so we don't need to call
3099
+ // entangleTransitionUpdate here.
3100
+ }
3101
+ }
3102
+
3103
+ markUpdateInDevTools(fiber, SyncLane, action);
3104
+}
3105
+
3106
function isRenderPhaseUpdate(fiber: Fiber): boolean {
3107
const alternate = fiber.alternate;
3108
return (
3206
(ContextOnlyDispatcher: Dispatcher).useHostTransitionStatus =
3207
throwInvalidHookError;
3208
}
3209
+if (enableAsyncActions) {
3210
+ (ContextOnlyDispatcher: Dispatcher).useOptimisticState =
3211
+ throwInvalidHookError;
3212
+}
3213
3214
const HooksDispatcherOnMount: Dispatcher = {
3215
readContext,
3245
(HooksDispatcherOnMount: Dispatcher).useHostTransitionStatus =
3246
useHostTransitionStatus;
3247
}
3248
+if (enableAsyncActions) {
3249
+ (HooksDispatcherOnMount: Dispatcher).useOptimisticState =
3250
+ mountOptimisticState;
3251
+}
3252
+
3253
const HooksDispatcherOnUpdate: Dispatcher = {
3254
readContext,
3255
3284
(HooksDispatcherOnUpdate: Dispatcher).useHostTransitionStatus =
3285
useHostTransitionStatus;
3286
}
3287
+if (enableAsyncActions) {
3288
+ (HooksDispatcherOnUpdate: Dispatcher).useOptimisticState =
3289
+ updateOptimisticState;
3290
+}
3291
3292
const HooksDispatcherOnRerender: Dispatcher = {
3293
readContext,
3323
(HooksDispatcherOnRerender: Dispatcher).useHostTransitionStatus =
3324
useHostTransitionStatus;
3325
}
3326
+if (enableAsyncActions) {
3327
+ (HooksDispatcherOnRerender: Dispatcher).useOptimisticState =
3328
+ rerenderOptimisticState;
3329
+}
3330
3331
let HooksDispatcherOnMountInDEV: Dispatcher | null = null;
3332
let HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher | null = null;
3517
(HooksDispatcherOnMountInDEV: Dispatcher).useHostTransitionStatus =
3518
useHostTransitionStatus;
3519
}
3520
+ if (enableAsyncActions) {
3521
+ (HooksDispatcherOnMountInDEV: Dispatcher).useOptimisticState =
3522
+ function useOptimisticState<S, A>(
3523
+ passthrough: S,
3524
+ reducer: ?(S, A) => S,
3525
+ ): [S, (A) => void] {
3526
+ currentHookNameInDev = 'useOptimisticState';
3527
+ mountHookTypesDev();
3528
+ return mountOptimisticState(passthrough, reducer);
3529
+ };
3530
+ }
3531
3532
HooksDispatcherOnMountWithHookTypesInDEV = {
3533
readContext<T>(context: ReactContext<T>): T {
3686
(HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher).useHostTransitionStatus =
3687
useHostTransitionStatus;
3688
}
3689
+ if (enableAsyncActions) {
3690
+ (HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher).useOptimisticState =
3691
+ function useOptimisticState<S, A>(
3692
+ passthrough: S,
3693
+ reducer: ?(S, A) => S,
3694
+ ): [S, (A) => void] {
3695
+ currentHookNameInDev = 'useOptimisticState';
3696
+ updateHookTypesDev();
3697
+ return mountOptimisticState(passthrough, reducer);
3698
+ };
3699
+ }
3700
3701
HooksDispatcherOnUpdateInDEV = {
3702
readContext<T>(context: ReactContext<T>): T {
3857
(HooksDispatcherOnUpdateInDEV: Dispatcher).useHostTransitionStatus =
3858
useHostTransitionStatus;
3859
}
3860
+ if (enableAsyncActions) {
3861
+ (HooksDispatcherOnUpdateInDEV: Dispatcher).useOptimisticState =
3862
+ function useOptimisticState<S, A>(
3863
+ passthrough: S,
3864
+ reducer: ?(S, A) => S,
3865
+ ): [S, (A) => void] {
3866
+ currentHookNameInDev = 'useOptimisticState';
3867
+ updateHookTypesDev();
3868
+ return updateOptimisticState(passthrough, reducer);
3869
+ };
3870
+ }
3871
3872
HooksDispatcherOnRerenderInDEV = {
3873
readContext<T>(context: ReactContext<T>): T {
4028
(HooksDispatcherOnRerenderInDEV: Dispatcher).useHostTransitionStatus =
4029
useHostTransitionStatus;
4030
}
4031
+ if (enableAsyncActions) {
4032
+ (HooksDispatcherOnRerenderInDEV: Dispatcher).useOptimisticState =
4033
+ function useOptimisticState<S, A>(
4034
+ passthrough: S,
4035
+ reducer: ?(S, A) => S,
4036
+ ): [S, (A) => void] {
4037
+ currentHookNameInDev = 'useOptimisticState';
4038
+ updateHookTypesDev();
4039
+ return rerenderOptimisticState(passthrough, reducer);
4040
+ };
4041
+ }
4042
4043
InvalidNestedHooksDispatcherOnMountInDEV = {
4044
readContext<T>(context: ReactContext<T>): T {
4221
(InvalidNestedHooksDispatcherOnMountInDEV: Dispatcher).useHostTransitionStatus =
4222
useHostTransitionStatus;
4223
}
4224
+ if (enableAsyncActions) {
4225
+ (InvalidNestedHooksDispatcherOnMountInDEV: Dispatcher).useOptimisticState =
4226
+ function useOptimisticState<S, A>(
4227
+ passthrough: S,
4228
+ reducer: ?(S, A) => S,
4229
+ ): [S, (A) => void] {
4230
+ currentHookNameInDev = 'useOptimisticState';
4231
+ warnInvalidHookAccess();
4232
+ mountHookTypesDev();
4233
+ return mountOptimisticState(passthrough, reducer);
4234
+ };
4235
+ }
4236
4237
InvalidNestedHooksDispatcherOnUpdateInDEV = {
4238
readContext<T>(context: ReactContext<T>): T {
4418
(InvalidNestedHooksDispatcherOnUpdateInDEV: Dispatcher).useHostTransitionStatus =
4419
useHostTransitionStatus;
4420
}
4421
+ if (enableAsyncActions) {
4422
+ (InvalidNestedHooksDispatcherOnUpdateInDEV: Dispatcher).useOptimisticState =
4423
+ function useOptimisticState<S, A>(
4424
+ passthrough: S,
4425
+ reducer: ?(S, A) => S,
4426
+ ): [S, (A) => void] {
4427
+ currentHookNameInDev = 'useOptimisticState';
4428
+ warnInvalidHookAccess();
4429
+ updateHookTypesDev();
4430
+ return updateOptimisticState(passthrough, reducer);
4431
+ };
4432
+ }
4433
4434
InvalidNestedHooksDispatcherOnRerenderInDEV = {
4435
readContext<T>(context: ReactContext<T>): T {
4615
(InvalidNestedHooksDispatcherOnRerenderInDEV: Dispatcher).useHostTransitionStatus =
4616
useHostTransitionStatus;
4617
}
4618
+ if (enableAsyncActions) {
4619
+ (InvalidNestedHooksDispatcherOnRerenderInDEV: Dispatcher).useOptimisticState =
4620
+ function useOptimisticState<S, A>(
4621
+ passthrough: S,
4622
+ reducer: ?(S, A) => S,
4623
+ ): [S, (A) => void] {
4624
+ currentHookNameInDev = 'useOptimisticState';
4625
+ warnInvalidHookAccess();
4626
+ updateHookTypesDev();
4627
+ return rerenderOptimisticState(passthrough, reducer);
4628
+ };
4629
+ }
4630
}