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

Scaffolding for useFormState #27270

Merged
merged 1 commit into from
Aug 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
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
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,7 @@ module.exports = {
$ReadOnlyArray: 'readonly',
$ArrayBufferView: 'readonly',
$Shape: 'readonly',
ReturnType: 'readonly',
AnimationFrameID: 'readonly',
// For Flow type annotation. Only `BigInt` is valid at runtime.
bigint: 'readonly',
Expand Down
14 changes: 14 additions & 0 deletions packages/react-dom-bindings/src/shared/ReactDOMFormActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,17 @@ export function useFormStatus(): FormStatus {
return dispatcher.useHostTransitionStatus();
}
}

export function useFormState<S, P>(
action: (S, P) => S,
initialState: S,
url?: string,
): [S, (P) => void] {
if (!(enableFormActions && enableAsyncActions)) {
throw new Error('Not implemented.');
} else {
const dispatcher = resolveDispatcher();
// $FlowFixMe[not-a-function] This is unstable, thus optional
return dispatcher.useFormState(action, initialState, url);
}
}
1 change: 1 addition & 0 deletions packages/react-dom/index.classic.fb.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export {
unstable_renderSubtreeIntoContainer,
unstable_runWithPriority, // DO NOT USE: Temporarily exposed to migrate off of Scheduler.runWithPriority.
useFormStatus as experimental_useFormStatus,
useFormState as experimental_useFormState,
prefetchDNS,
preconnect,
preload,
Expand Down
1 change: 1 addition & 0 deletions packages/react-dom/index.experimental.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export {
unstable_renderSubtreeIntoContainer,
unstable_runWithPriority, // DO NOT USE: Temporarily exposed to migrate off of Scheduler.runWithPriority.
useFormStatus as experimental_useFormStatus,
useFormState as experimental_useFormState,
prefetchDNS,
preconnect,
preload,
Expand Down
1 change: 1 addition & 0 deletions packages/react-dom/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export {
unstable_renderSubtreeIntoContainer,
unstable_runWithPriority, // DO NOT USE: Temporarily exposed to migrate off of Scheduler.runWithPriority.
useFormStatus as experimental_useFormStatus,
useFormState as experimental_useFormState,
prefetchDNS,
preconnect,
preload,
Expand Down
1 change: 1 addition & 0 deletions packages/react-dom/index.modern.fb.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export {
unstable_createEventHandle,
unstable_runWithPriority, // DO NOT USE: Temporarily exposed to migrate off of Scheduler.runWithPriority.
useFormStatus as experimental_useFormStatus,
useFormState as experimental_useFormState,
prefetchDNS,
preconnect,
preload,
Expand Down
1 change: 1 addition & 0 deletions packages/react-dom/server-rendering-stub.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,6 @@ export {
preload,
preinit,
experimental_useFormStatus,
experimental_useFormState,
unstable_batchedUpdates,
} from './src/server/ReactDOMServerRenderingStub';
24 changes: 24 additions & 0 deletions packages/react-dom/src/__tests__/ReactDOMFizzForm-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ let ReactDOMServer;
let ReactDOMClient;
let useFormStatus;
let useOptimistic;
let useFormState;

describe('ReactDOMFizzForm', () => {
beforeEach(() => {
Expand All @@ -31,6 +32,7 @@ describe('ReactDOMFizzForm', () => {
ReactDOMServer = require('react-dom/server.browser');
ReactDOMClient = require('react-dom/client');
useFormStatus = require('react-dom').experimental_useFormStatus;
useFormState = require('react-dom').experimental_useFormState;
useOptimistic = require('react').experimental_useOptimistic;
act = require('internal-test-utils').act;
container = document.createElement('div');
Expand Down Expand Up @@ -470,6 +472,28 @@ describe('ReactDOMFizzForm', () => {
expect(container.textContent).toBe('hi');
});

// @gate enableFormActions
// @gate enableAsyncActions
it('useFormState returns initial state', async () => {
async function action(state) {
return state;
}

function App() {
const [state] = useFormState(action, 0);
return state;
}

const stream = await ReactDOMServer.renderToReadableStream(<App />);
await readIntoContainer(stream);
expect(container.textContent).toBe('0');

await act(async () => {
ReactDOMClient.hydrateRoot(container, <App />);
});
expect(container.textContent).toBe('0');
});

// @gate enableFormActions
it('can provide a custom action on the server for actions', async () => {
const ref = React.createRef();
Expand Down
22 changes: 22 additions & 0 deletions packages/react-dom/src/__tests__/ReactDOMForm-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ describe('ReactDOMForm', () => {
let startTransition;
let textCache;
let useFormStatus;
let useFormState;

beforeEach(() => {
jest.resetModules();
Expand All @@ -53,6 +54,7 @@ describe('ReactDOMForm', () => {
Suspense = React.Suspense;
startTransition = React.startTransition;
useFormStatus = ReactDOM.experimental_useFormStatus;
useFormState = ReactDOM.experimental_useFormState;
container = document.createElement('div');
document.body.appendChild(container);

Expand Down Expand Up @@ -969,4 +971,24 @@ describe('ReactDOMForm', () => {
'A React form was unexpectedly submitted. If you called form.submit()',
);
});

// @gate enableFormActions
// @gate enableAsyncActions
test('useFormState exists', async () => {
// TODO: Not yet implemented. This just tests that the API is wired up.

async function action(state) {
return state;
}

function App() {
const [state] = useFormState(action, 0);
return <Text text={state} />;
}

const root = ReactDOMClient.createRoot(container);
await act(() => root.render(<App />));
assertLog([0]);
expect(container.textContent).toBe('0');
});
});
5 changes: 4 additions & 1 deletion packages/react-dom/src/client/ReactDOM.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,10 @@ export {
preinit,
preinitModule,
} from '../shared/ReactDOMFloat';
export {useFormStatus} from 'react-dom-bindings/src/shared/ReactDOMFormActions';
export {
useFormStatus,
useFormState,
} from 'react-dom-bindings/src/shared/ReactDOMFormActions';

if (__DEV__) {
if (
Expand Down
5 changes: 4 additions & 1 deletion packages/react-dom/src/server/ReactDOMServerRenderingStub.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ export {
preconnect,
prefetchDNS,
} from '../shared/ReactDOMFloat';
export {useFormStatus as experimental_useFormStatus} from 'react-dom-bindings/src/shared/ReactDOMFormActions';
export {
useFormStatus as experimental_useFormStatus,
useFormState as experimental_useFormState,
} from 'react-dom-bindings/src/shared/ReactDOMFormActions';

export function createPortal() {
throw new Error(
Expand Down
108 changes: 108 additions & 0 deletions packages/react-reconciler/src/ReactFiberHooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -1835,6 +1835,37 @@ function rerenderOptimistic<S, A>(
return [passthrough, dispatch];
}

function TODO_formStateDispatch() {
throw new Error('Not implemented.');
}

function mountFormState<S, P>(
action: (S, P) => S,
initialState: S,
url?: string,
): [S, (P) => void] {
// TODO: Not yet implemented
return [initialState, TODO_formStateDispatch];
}

function updateFormState<S, P>(
action: (S, P) => S,
initialState: S,
url?: string,
): [S, (P) => void] {
// TODO: Not yet implemented
return [initialState, TODO_formStateDispatch];
}

function rerenderFormState<S, P>(
action: (S, P) => S,
initialState: S,
url?: string,
): [S, (P) => void] {
// TODO: Not yet implemented
return [initialState, TODO_formStateDispatch];
}

function pushEffect(
tag: HookFlags,
create: () => (() => void) | void,
Expand Down Expand Up @@ -2981,6 +3012,7 @@ if (enableUseEffectEventHook) {
if (enableFormActions && enableAsyncActions) {
(ContextOnlyDispatcher: Dispatcher).useHostTransitionStatus =
throwInvalidHookError;
(ContextOnlyDispatcher: Dispatcher).useFormState = throwInvalidHookError;
}
if (enableAsyncActions) {
(ContextOnlyDispatcher: Dispatcher).useOptimistic = throwInvalidHookError;
Expand Down Expand Up @@ -3018,6 +3050,7 @@ if (enableUseEffectEventHook) {
if (enableFormActions && enableAsyncActions) {
(HooksDispatcherOnMount: Dispatcher).useHostTransitionStatus =
useHostTransitionStatus;
(HooksDispatcherOnMount: Dispatcher).useFormState = mountFormState;
}
if (enableAsyncActions) {
(HooksDispatcherOnMount: Dispatcher).useOptimistic = mountOptimistic;
Expand Down Expand Up @@ -3055,6 +3088,7 @@ if (enableUseEffectEventHook) {
if (enableFormActions && enableAsyncActions) {
(HooksDispatcherOnUpdate: Dispatcher).useHostTransitionStatus =
useHostTransitionStatus;
(HooksDispatcherOnUpdate: Dispatcher).useFormState = updateFormState;
}
if (enableAsyncActions) {
(HooksDispatcherOnUpdate: Dispatcher).useOptimistic = updateOptimistic;
Expand Down Expand Up @@ -3092,6 +3126,7 @@ if (enableUseEffectEventHook) {
if (enableFormActions && enableAsyncActions) {
(HooksDispatcherOnRerender: Dispatcher).useHostTransitionStatus =
useHostTransitionStatus;
(HooksDispatcherOnRerender: Dispatcher).useFormState = rerenderFormState;
}
if (enableAsyncActions) {
(HooksDispatcherOnRerender: Dispatcher).useOptimistic = rerenderOptimistic;
Expand Down Expand Up @@ -3276,6 +3311,16 @@ if (__DEV__) {
if (enableFormActions && enableAsyncActions) {
(HooksDispatcherOnMountInDEV: Dispatcher).useHostTransitionStatus =
useHostTransitionStatus;
(HooksDispatcherOnMountInDEV: Dispatcher).useFormState =
function useFormState<S, P>(
action: (S, P) => S,
initialState: S,
url?: string,
): [S, (P) => void] {
currentHookNameInDev = 'useFormState';
mountHookTypesDev();
return mountFormState(action, initialState, url);
};
}
if (enableAsyncActions) {
(HooksDispatcherOnMountInDEV: Dispatcher).useOptimistic =
Expand Down Expand Up @@ -3436,6 +3481,16 @@ if (__DEV__) {
if (enableFormActions && enableAsyncActions) {
(HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher).useHostTransitionStatus =
useHostTransitionStatus;
(HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher).useFormState =
function useFormState<S, P>(
action: (S, P) => S,
initialState: S,
url?: string,
): [S, (P) => void] {
currentHookNameInDev = 'useFormState';
updateHookTypesDev();
return mountFormState(action, initialState, url);
};
}
if (enableAsyncActions) {
(HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher).useOptimistic =
Expand Down Expand Up @@ -3598,6 +3653,16 @@ if (__DEV__) {
if (enableFormActions && enableAsyncActions) {
(HooksDispatcherOnUpdateInDEV: Dispatcher).useHostTransitionStatus =
useHostTransitionStatus;
(HooksDispatcherOnUpdateInDEV: Dispatcher).useFormState =
function useFormState<S, P>(
action: (S, P) => S,
initialState: S,
url?: string,
): [S, (P) => void] {
currentHookNameInDev = 'useFormState';
updateHookTypesDev();
return updateFormState(action, initialState, url);
};
}
if (enableAsyncActions) {
(HooksDispatcherOnUpdateInDEV: Dispatcher).useOptimistic =
Expand Down Expand Up @@ -3760,6 +3825,16 @@ if (__DEV__) {
if (enableFormActions && enableAsyncActions) {
(HooksDispatcherOnRerenderInDEV: Dispatcher).useHostTransitionStatus =
useHostTransitionStatus;
(HooksDispatcherOnRerenderInDEV: Dispatcher).useFormState =
function useFormState<S, P>(
action: (S, P) => S,
initialState: S,
url?: string,
): [S, (P) => void] {
currentHookNameInDev = 'useFormState';
updateHookTypesDev();
return rerenderFormState(action, initialState, url);
};
}
if (enableAsyncActions) {
(HooksDispatcherOnRerenderInDEV: Dispatcher).useOptimistic =
Expand Down Expand Up @@ -3943,6 +4018,17 @@ if (__DEV__) {
if (enableFormActions && enableAsyncActions) {
(InvalidNestedHooksDispatcherOnMountInDEV: Dispatcher).useHostTransitionStatus =
useHostTransitionStatus;
(InvalidNestedHooksDispatcherOnMountInDEV: Dispatcher).useFormState =
function useFormState<S, P>(
action: (S, P) => S,
initialState: S,
url?: string,
): [S, (P) => void] {
currentHookNameInDev = 'useFormState';
warnInvalidHookAccess();
mountHookTypesDev();
return mountFormState(action, initialState, url);
};
}
if (enableAsyncActions) {
(InvalidNestedHooksDispatcherOnMountInDEV: Dispatcher).useOptimistic =
Expand Down Expand Up @@ -4130,6 +4216,17 @@ if (__DEV__) {
if (enableFormActions && enableAsyncActions) {
(InvalidNestedHooksDispatcherOnUpdateInDEV: Dispatcher).useHostTransitionStatus =
useHostTransitionStatus;
(InvalidNestedHooksDispatcherOnUpdateInDEV: Dispatcher).useFormState =
function useFormState<S, P>(
action: (S, P) => S,
initialState: S,
url?: string,
): [S, (P) => void] {
currentHookNameInDev = 'useFormState';
warnInvalidHookAccess();
updateHookTypesDev();
return updateFormState(action, initialState, url);
};
}
if (enableAsyncActions) {
(InvalidNestedHooksDispatcherOnUpdateInDEV: Dispatcher).useOptimistic =
Expand Down Expand Up @@ -4317,6 +4414,17 @@ if (__DEV__) {
if (enableFormActions && enableAsyncActions) {
(InvalidNestedHooksDispatcherOnRerenderInDEV: Dispatcher).useHostTransitionStatus =
useHostTransitionStatus;
(InvalidNestedHooksDispatcherOnRerenderInDEV: Dispatcher).useFormState =
function useFormState<S, P>(
action: (S, P) => S,
initialState: S,
url?: string,
): [S, (P) => void] {
currentHookNameInDev = 'useFormState';
warnInvalidHookAccess();
updateHookTypesDev();
return rerenderFormState(action, initialState, url);
};
}
if (enableAsyncActions) {
(InvalidNestedHooksDispatcherOnRerenderInDEV: Dispatcher).useOptimistic =
Expand Down
8 changes: 7 additions & 1 deletion packages/react-reconciler/src/ReactInternalTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ export type HookType =
| 'useSyncExternalStore'
| 'useId'
| 'useCacheRefresh'
| 'useOptimistic';
| 'useOptimistic'
| 'useFormState';

export type ContextDependency<T> = {
context: ReactContext<T>,
Expand Down Expand Up @@ -413,6 +414,11 @@ export type Dispatcher = {
passthrough: S,
reducer: ?(S, A) => S,
) => [S, (A) => void],
useFormState?: <S, P>(
action: (S, P) => S,
initialState: S,
url?: string,
) => [S, (P) => void],
};

export type CacheDispatcher = {
Expand Down
Loading