Add example of useActionState handling execution order (#7733)

This commit is contained in:
Ajit
2025-05-16 23:57:51 +05:30
committed by GitHub
parent 8b2fe2b1ae
commit 00587d6991

View File

@@ -1948,3 +1948,162 @@ When clicking multiple times, it's possible for previous requests to finish afte
This is expected, because Actions within a Transition do not guarantee execution order. For common use cases, React provides higher-level abstractions like [`useActionState`](/reference/react/useActionState) and [`<form>` actions](/reference/react-dom/components/form) that handle ordering for you. For advanced use cases, you'll need to implement your own queuing and abort logic to handle this.
Example of `useActionState` handling execution order:
<Sandpack>
```json package.json hidden
{
"dependencies": {
"react": "beta",
"react-dom": "beta"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
}
}
```
```js src/App.js
import { useState, useActionState } from "react";
import { updateQuantity } from "./api";
import Item from "./Item";
import Total from "./Total";
export default function App({}) {
// Store the actual quantity in separate state to show the mismatch.
const [clientQuantity, setClientQuantity] = useState(1);
const [quantity, updateQuantityAction, isPending] = useActionState(
async (prevState, payload) => {
setClientQuantity(payload);
const savedQuantity = await updateQuantity(payload);
return savedQuantity; // Return the new quantity to update the state
},
1 // Initial quantity
);
return (
<div>
<h1>Checkout</h1>
<Item action={updateQuantityAction}/>
<hr />
<Total clientQuantity={clientQuantity} savedQuantity={quantity} isPending={isPending} />
</div>
);
}
```
```js src/Item.js
import {startTransition} from 'react';
export default function Item({action}) {
function handleChange(e) {
// Update the quantity in an Action.
startTransition(() => {
action(e.target.value);
});
}
return (
<div className="item">
<span>Eras Tour Tickets</span>
<label htmlFor="name">Quantity: </label>
<input
type="number"
onChange={handleChange}
defaultValue={1}
min={1}
/>
</div>
)
}
```
```js src/Total.js
const intl = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD"
});
export default function Total({ clientQuantity, savedQuantity, isPending }) {
return (
<div className="total">
<span>Total:</span>
<div>
<div>
{isPending
? "🌀 Updating..."
: `${intl.format(savedQuantity * 9999)}`}
</div>
<div className="error">
{!isPending &&
clientQuantity !== savedQuantity &&
`Wrong total, expected: ${intl.format(clientQuantity * 9999)}`}
</div>
</div>
</div>
);
}
```
```js src/api.js
let firstRequest = true;
export async function updateQuantity(newName) {
return new Promise((resolve, reject) => {
if (firstRequest === true) {
firstRequest = false;
setTimeout(() => {
firstRequest = true;
resolve(newName);
// Simulate every other request being slower
}, 1000);
} else {
setTimeout(() => {
resolve(newName);
}, 50);
}
});
}
```
```css
.item {
display: flex;
align-items: center;
justify-content: start;
}
.item label {
flex: 1;
text-align: right;
}
.item input {
margin-left: 4px;
width: 60px;
padding: 4px;
}
.total {
height: 50px;
line-height: 25px;
display: flex;
align-content: center;
justify-content: space-between;
}
.total div {
display: flex;
flex-direction: column;
align-items: flex-end;
}
.error {
color: red;
}
```
</Sandpack>