Skip to content

Commit

Permalink
Parameterize pending state type of host transitions
Browse files Browse the repository at this point in the history
Updates the internals of async form actions so they can use a custom
pending state type, instead of a boolean. I'm not sure this is how we'll
end up doing it once optimistic state is implemented, but it fits with
how we handle the isPending state of useTransition.

The next step is to connect this to useFormStatus, which will read
the value of the nearest pending form state using context.
  • Loading branch information
acdlite committed Apr 26, 2023
1 parent 3b2f716 commit 432bcfb
Show file tree
Hide file tree
Showing 11 changed files with 71 additions and 22 deletions.
2 changes: 2 additions & 0 deletions packages/react-art/src/ReactFiberConfigART.js
Original file line number Diff line number Diff line change
Expand Up @@ -479,3 +479,5 @@ export function suspendInstance(type, props) {}
export function waitForCommitToBeReady() {
return null;
}

export const NotPendingTransition = null;
6 changes: 6 additions & 0 deletions packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ import type {
} from 'react-reconciler/src/ReactTestSelectors';
import type {ReactScopeInstance} from 'shared/ReactTypes';
import type {AncestorInfoDev} from './validateDOMNesting';
import type {FormStatus} from 'react-dom-bindings/src/shared/ReactDOMFormActions';

import {NotPending} from 'react-dom-bindings/src/shared/ReactDOMFormActions';
import {getCurrentRootHostContainer} from 'react-reconciler/src/ReactFiberHostContext';
import {DefaultEventPriority} from 'react-reconciler/src/ReactEventPriorities';
// TODO: Remove this deep import when we delete the legacy root API
Expand Down Expand Up @@ -164,6 +166,8 @@ export type TimeoutHandle = TimeoutID;
export type NoTimeout = -1;
export type RendererInspectionConfig = $ReadOnly<{}>;

export type TransitionStatus = FormStatus;

type SelectionInformation = {
focusedElem: null | HTMLElement,
selectionRange: mixed,
Expand Down Expand Up @@ -3448,3 +3452,5 @@ function insertStylesheetIntoRoot(
}
resource.state.loading |= Inserted;
}

export const NotPendingTransition: TransitionStatus = NotPending;
Original file line number Diff line number Diff line change
Expand Up @@ -473,7 +473,7 @@ function replayUnblockedFormActions(formReplayingQueue: FormReplayingQueue) {
// We're ready to replay this. Let's delete it from the queue.
formReplayingQueue.splice(i, 3);
i -= 3;
dispatchReplayedFormAction(formInst, submitterOrAction, formData);
dispatchReplayedFormAction(formInst, form, submitterOrAction, formData);
// Continue without incrementing the index.
continue;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import type {DOMEventName} from '../DOMEventNames';
import type {DispatchQueue} from '../DOMPluginEventSystem';
import type {EventSystemFlags} from '../EventSystemFlags';
import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
import type {FormStatus} from 'react-dom-bindings/src/shared/ReactDOMFormActions';

import {getFiberCurrentPropsFromNode} from '../../client/ReactDOMComponentTree';
import {startHostTransition} from 'react-reconciler/src/ReactFiberReconciler';
Expand Down Expand Up @@ -98,7 +99,16 @@ function extractEvents(
formData = new FormData(form);
}

startHostTransition(formInst, action, formData);
const pendingState: FormStatus = {
pending: true,
data: formData,
method: form.method,
action: action,
};
if (__DEV__) {
Object.freeze(pendingState);
}
startHostTransition(formInst, pendingState, action, formData);
}

dispatchQueue.push({
Expand All @@ -117,8 +127,18 @@ export {extractEvents};

export function dispatchReplayedFormAction(
formInst: Fiber,
form: HTMLFormElement,
action: FormData => void | Promise<void>,
formData: FormData,
): void {
startHostTransition(formInst, action, formData);
const pendingState: FormStatus = {
pending: true,
data: formData,
method: form.method,
action: action,
};
if (__DEV__) {
Object.freeze(pendingState);
}
startHostTransition(formInst, pendingState, action, formData);
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const sharedNotPendingObject = {
action: null,
};

const NotPending: FormStatus = __DEV__
export const NotPending: FormStatus = __DEV__
? Object.freeze(sharedNotPendingObject)
: sharedNotPendingObject;

Expand Down
3 changes: 3 additions & 0 deletions packages/react-native-renderer/src/ReactFiberConfigFabric.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ export type UpdatePayload = Object;

export type TimeoutHandle = TimeoutID;
export type NoTimeout = -1;
export type TransitionStatus = mixed;

export type RendererInspectionConfig = $ReadOnly<{
// Deprecated. Replaced with getInspectorDataForViewAtPoint.
Expand Down Expand Up @@ -489,3 +490,5 @@ export function suspendInstance(type: Type, props: Props): void {}
export function waitForCommitToBeReady(): null {
return null;
}

export const NotPendingTransition: TransitionStatus = null;
3 changes: 3 additions & 0 deletions packages/react-native-renderer/src/ReactFiberConfigNative.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export type ChildSet = void; // Unused

export type TimeoutHandle = TimeoutID;
export type NoTimeout = -1;
export type TransitionStatus = mixed;

export type RendererInspectionConfig = $ReadOnly<{
// Deprecated. Replaced with getInspectorDataForViewAtPoint.
Expand Down Expand Up @@ -542,3 +543,5 @@ export function suspendInstance(type: Type, props: Props): void {}
export function waitForCommitToBeReady(): null {
return null;
}

export const NotPendingTransition: TransitionStatus = null;
4 changes: 4 additions & 0 deletions packages/react-noop-renderer/src/createReactNoop.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ type SuspenseyCommitSubscription = {
commit: null | (() => void),
};

export type TransitionStatus = mixed;

const NO_CONTEXT = {};
const UPPERCASE_CONTEXT = {};
const UPDATE_SIGNAL = {};
Expand Down Expand Up @@ -629,6 +631,8 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
},

waitForCommitToBeReady,

NotPendingTransition: (null: TransitionStatus),
};

const hostConfig = useMutation
Expand Down
42 changes: 24 additions & 18 deletions packages/react-reconciler/src/ReactFiberHooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ import type {
import type {Lanes, Lane} from './ReactFiberLane';
import type {HookFlags} from './ReactHookEffectTags';
import type {Flags} from './ReactFiberFlags';
import type {TransitionStatus} from './ReactFiberConfig';

import {NotPendingTransition as NoPendingHostTransition} from './ReactFiberConfig';
import ReactSharedInternals from 'shared/ReactSharedInternals';
import {
enableDebugTracing,
Expand Down Expand Up @@ -757,9 +759,9 @@ export function renderTransitionAwareHostComponentWithHooks(
current: Fiber | null,
workInProgress: Fiber,
lanes: Lanes,
): boolean {
): TransitionStatus {
if (!(enableFormActions && enableAsyncActions)) {
return false;
throw new Error('Not implemented.');
}
return renderWithHooks(
current,
Expand All @@ -771,16 +773,19 @@ export function renderTransitionAwareHostComponentWithHooks(
);
}

export function TransitionAwareHostComponent(): boolean {
export function TransitionAwareHostComponent(): TransitionStatus {
if (!(enableFormActions && enableAsyncActions)) {
return false;
throw new Error('Not implemented.');
}
const dispatcher = ReactCurrentDispatcher.current;
const [booleanOrThenable] = dispatcher.useState();
return typeof booleanOrThenable === 'boolean'
? booleanOrThenable
: // This will suspend until the async action scope has finished.
useThenable(booleanOrThenable);
const [maybeThenable] = dispatcher.useState();
if (typeof maybeThenable.then === 'function') {
const thenable: Thenable<TransitionStatus> = (maybeThenable: any);
return useThenable(thenable);
} else {
const status: TransitionStatus = maybeThenable;
return status;
}
}

export function checkDidRenderIdHook(): boolean {
Expand Down Expand Up @@ -2520,6 +2525,7 @@ function startTransition<S>(

export function startHostTransition<F>(
formFiber: Fiber,
pendingState: TransitionStatus,
callback: F => mixed,
formData: F,
): void {
Expand Down Expand Up @@ -2551,24 +2557,24 @@ export function startHostTransition<F>(
// Create the state hook used by TransitionAwareHostComponent. This is
// essentially an inlined version of mountState.
const queue: UpdateQueue<
Thenable<boolean> | boolean,
Thenable<boolean> | boolean,
Thenable<TransitionStatus> | TransitionStatus,
Thenable<TransitionStatus> | TransitionStatus,
> = {
pending: null,
lanes: NoLanes,
dispatch: null,
lastRenderedReducer: basicStateReducer,
lastRenderedState: false,
lastRenderedState: NoPendingHostTransition,
};
const stateHook: Hook = {
memoizedState: false,
baseState: false,
memoizedState: NoPendingHostTransition,
baseState: NoPendingHostTransition,
baseQueue: null,
queue: queue,
next: null,
};

const dispatch: (Thenable<boolean> | boolean) => void =
const dispatch: (Thenable<TransitionStatus> | TransitionStatus) => void =
(dispatchSetState.bind(null, formFiber, queue): any);
setPending = queue.dispatch = dispatch;

Expand All @@ -2582,14 +2588,14 @@ export function startHostTransition<F>(
} else {
// This fiber was already upgraded to be stateful.
const stateHook: Hook = formFiber.memoizedState;
const dispatch: (Thenable<boolean> | boolean) => void =
const dispatch: (Thenable<TransitionStatus> | TransitionStatus) => void =
stateHook.queue.dispatch;
setPending = dispatch;
}

startTransition(
true,
false,
pendingState,
NoPendingHostTransition,
setPending,
// TODO: We can avoid this extra wrapper, somehow. Figure out layering
// once more of this function is implemented.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export opaque type ChildSet = mixed; // eslint-disable-line no-undef
export opaque type TimeoutHandle = mixed; // eslint-disable-line no-undef
export opaque type NoTimeout = mixed; // eslint-disable-line no-undef
export opaque type RendererInspectionConfig = mixed; // eslint-disable-line no-undef
export opaque type TransitionStatus = mixed; // eslint-disable-line no-undef
export type EventResponder = any;

export const getPublicInstance = $$$config.getPublicInstance;
Expand Down Expand Up @@ -75,6 +76,7 @@ export const preloadInstance = $$$config.preloadInstance;
export const startSuspendingCommit = $$$config.startSuspendingCommit;
export const suspendInstance = $$$config.suspendInstance;
export const waitForCommitToBeReady = $$$config.waitForCommitToBeReady;
export const NotPendingTransition = $$$config.NotPendingTransition;

// -------------------
// Microtasks
Expand Down
3 changes: 3 additions & 0 deletions packages/react-test-renderer/src/ReactFiberConfigTestHost.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export type NoTimeout = -1;
export type EventResponder = any;

export type RendererInspectionConfig = $ReadOnly<{}>;
export type TransitionStatus = mixed;

export * from 'react-reconciler/src/ReactFiberConfigWithNoPersistence';
export * from 'react-reconciler/src/ReactFiberConfigWithNoHydration';
Expand Down Expand Up @@ -343,3 +344,5 @@ export function suspendInstance(type: Type, props: Props): void {}
export function waitForCommitToBeReady(): null {
return null;
}

export const NotPendingTransition: TransitionStatus = null;

0 comments on commit 432bcfb

Please sign in to comment.