Skip to content

Commit

Permalink
Client implementation of useFormState (#27278)
Browse files Browse the repository at this point in the history
This implements useFormState in Fiber. (It does not include any
progressive enhancement features; those will be added later.)

useFormState is a hook for tracking state produced by async actions. It
has a signature similar to useReducer, but instead of a reducer, it
accepts an async action function.

```js
async function action(prevState, payload) {
  // ..
}
const [state, dispatch] = useFormState(action, initialState)
```

Calling dispatch runs the async action and updates the state to the
returned value.

Async actions run before React's render cycle, so unlike reducers, they
can contain arbitrary side effects.

DiffTrain build for commit 456d153.
  • Loading branch information
acdlite committed Aug 28, 2023
1 parent e059d6a commit 79eaef5
Show file tree
Hide file tree
Showing 13 changed files with 778 additions and 763 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* @noflow
* @nolint
* @preventMunge
* @generated SignedSource<<c9230720afe9095de02a7a43b74e1202>>
* @generated SignedSource<<3545510f0ae198cd36a14af654d7cf69>>
*/

'use strict';
Expand Down Expand Up @@ -6409,97 +6409,102 @@ var currentEntangledListeners = null; // The number of pending async actions in
var currentEntangledPendingCount = 0; // The transition lane shared by all updates in the entangled scope.

var currentEntangledLane = NoLane;
function requestAsyncActionContext(actionReturnValue, finishedState) {
if (
actionReturnValue !== null &&
typeof actionReturnValue === "object" &&
typeof actionReturnValue.then === "function"
) {
// This is an async action.
//
// Return a thenable that resolves once the action scope (i.e. the async
// function passed to startTransition) has finished running.
var thenable = actionReturnValue;
var entangledListeners;

if (currentEntangledListeners === null) {
// There's no outer async action scope. Create a new one.
entangledListeners = currentEntangledListeners = [];
currentEntangledPendingCount = 0;
currentEntangledLane = requestTransitionLane();
} else {
entangledListeners = currentEntangledListeners;
function requestAsyncActionContext(
actionReturnValue, // If this is provided, this resulting thenable resolves to this value instead
// of the return value of the action. This is a perf trick to avoid composing
// an extra async function.
overrideReturnValue
) {
// This is an async action.
//
// Return a thenable that resolves once the action scope (i.e. the async
// function passed to startTransition) has finished running.
var thenable = actionReturnValue;
var entangledListeners;

if (currentEntangledListeners === null) {
// There's no outer async action scope. Create a new one.
entangledListeners = currentEntangledListeners = [];
currentEntangledPendingCount = 0;
currentEntangledLane = requestTransitionLane();
} else {
entangledListeners = currentEntangledListeners;
}

currentEntangledPendingCount++; // Create a thenable that represents the result of this action, but doesn't
// resolve until the entire entangled scope has finished.
//
// Expressed using promises:
// const [thisResult] = await Promise.all([thisAction, entangledAction]);
// return thisResult;

var resultThenable = createResultThenable(entangledListeners);
var resultStatus = "pending";
var resultValue;
var rejectedReason;
thenable.then(
function (value) {
resultStatus = "fulfilled";
resultValue = overrideReturnValue !== null ? overrideReturnValue : value;
pingEngtangledActionScope();
},
function (error) {
resultStatus = "rejected";
rejectedReason = error;
pingEngtangledActionScope();
}
); // Attach a listener to fill in the result.

currentEntangledPendingCount++;
var resultStatus = "pending";
var rejectedReason;
thenable.then(
function () {
resultStatus = "fulfilled";
pingEngtangledActionScope();
},
function (error) {
resultStatus = "rejected";
rejectedReason = error;
pingEngtangledActionScope();
entangledListeners.push(function () {
switch (resultStatus) {
case "fulfilled": {
var fulfilledThenable = resultThenable;
fulfilledThenable.status = "fulfilled";
fulfilledThenable.value = resultValue;
break;
}
); // Create a thenable that represents the result of this action, but doesn't
// resolve until the entire entangled scope has finished.
//
// Expressed using promises:
// const [thisResult] = await Promise.all([thisAction, entangledAction]);
// return thisResult;

var resultThenable = createResultThenable(entangledListeners); // Attach a listener to fill in the result.

entangledListeners.push(function () {
switch (resultStatus) {
case "fulfilled": {
var fulfilledThenable = resultThenable;
fulfilledThenable.status = "fulfilled";
fulfilledThenable.value = finishedState;
break;
}

case "rejected": {
var rejectedThenable = resultThenable;
rejectedThenable.status = "rejected";
rejectedThenable.reason = rejectedReason;
break;
}
case "rejected": {
var rejectedThenable = resultThenable;
rejectedThenable.status = "rejected";
rejectedThenable.reason = rejectedReason;
break;
}

case "pending":
default: {
// The listener above should have been called first, so `resultStatus`
// should already be set to the correct value.
throw new Error(
"Thenable should have already resolved. This " +
"is a bug in React."
);
}
case "pending":
default: {
// The listener above should have been called first, so `resultStatus`
// should already be set to the correct value.
throw new Error(
"Thenable should have already resolved. This " + "is a bug in React."
);
}
}
});
return resultThenable;
}
function requestSyncActionContext(
actionReturnValue, // If this is provided, this resulting thenable resolves to this value instead
// of the return value of the action. This is a perf trick to avoid composing
// an extra async function.
overrideReturnValue
) {
var resultValue =
overrideReturnValue !== null ? overrideReturnValue : actionReturnValue; // This is not an async action, but it may be part of an outer async action.

if (currentEntangledListeners === null) {
return resultValue;
} else {
// Return a thenable that does not resolve until the entangled actions
// have finished.
var entangledListeners = currentEntangledListeners;
var resultThenable = createResultThenable(entangledListeners);
entangledListeners.push(function () {
var fulfilledThenable = resultThenable;
fulfilledThenable.status = "fulfilled";
fulfilledThenable.value = resultValue;
});
return resultThenable;
} else {
// This is not an async action, but it may be part of an outer async action.
if (currentEntangledListeners === null) {
return finishedState;
} else {
// Return a thenable that does not resolve until the entangled actions
// have finished.
var _entangledListeners = currentEntangledListeners;

var _resultThenable = createResultThenable(_entangledListeners);

_entangledListeners.push(function () {
var fulfilledThenable = _resultThenable;
fulfilledThenable.status = "fulfilled";
fulfilledThenable.value = finishedState;
});

return _resultThenable;
}
}
}

Expand Down Expand Up @@ -8160,7 +8165,7 @@ function startTransition(
}

try {
var returnValue, maybeThenable;
var returnValue, thenable, entangledResult, _entangledResult;
if (enableAsyncActions);
else {
// Async actions are not enabled.
Expand Down Expand Up @@ -23981,7 +23986,7 @@ function createFiberRoot(
return root;
}

var ReactVersion = "18.3.0-canary-9a01c8b54-20230828";
var ReactVersion = "18.3.0-canary-456d153bb-20230828";

// Might add PROFILE later.

Expand Down
Loading

0 comments on commit 79eaef5

Please sign in to comment.