Skip to content

Commit 00587d6

Browse files
authored
Add example of useActionState handling execution order (#7733)
1 parent 8b2fe2b commit 00587d6

File tree

1 file changed

+159
-0
lines changed

1 file changed

+159
-0
lines changed

src/content/reference/react/useTransition.md

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1948,3 +1948,162 @@ When clicking multiple times, it's possible for previous requests to finish afte
19481948
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.
19491949
19501950
1951+
Example of `useActionState` handling execution order:
1952+
1953+
<Sandpack>
1954+
1955+
```json package.json hidden
1956+
{
1957+
"dependencies": {
1958+
"react": "beta",
1959+
"react-dom": "beta"
1960+
},
1961+
"scripts": {
1962+
"start": "react-scripts start",
1963+
"build": "react-scripts build",
1964+
"test": "react-scripts test --env=jsdom",
1965+
"eject": "react-scripts eject"
1966+
}
1967+
}
1968+
```
1969+
1970+
```js src/App.js
1971+
import { useState, useActionState } from "react";
1972+
import { updateQuantity } from "./api";
1973+
import Item from "./Item";
1974+
import Total from "./Total";
1975+
1976+
export default function App({}) {
1977+
// Store the actual quantity in separate state to show the mismatch.
1978+
const [clientQuantity, setClientQuantity] = useState(1);
1979+
const [quantity, updateQuantityAction, isPending] = useActionState(
1980+
async (prevState, payload) => {
1981+
setClientQuantity(payload);
1982+
const savedQuantity = await updateQuantity(payload);
1983+
return savedQuantity; // Return the new quantity to update the state
1984+
},
1985+
1 // Initial quantity
1986+
);
1987+
1988+
return (
1989+
<div>
1990+
<h1>Checkout</h1>
1991+
<Item action={updateQuantityAction}/>
1992+
<hr />
1993+
<Total clientQuantity={clientQuantity} savedQuantity={quantity} isPending={isPending} />
1994+
</div>
1995+
);
1996+
}
1997+
1998+
```
1999+
2000+
```js src/Item.js
2001+
import {startTransition} from 'react';
2002+
2003+
export default function Item({action}) {
2004+
function handleChange(e) {
2005+
// Update the quantity in an Action.
2006+
startTransition(() => {
2007+
action(e.target.value);
2008+
});
2009+
}
2010+
return (
2011+
<div className="item">
2012+
<span>Eras Tour Tickets</span>
2013+
<label htmlFor="name">Quantity: </label>
2014+
<input
2015+
type="number"
2016+
onChange={handleChange}
2017+
defaultValue={1}
2018+
min={1}
2019+
/>
2020+
</div>
2021+
)
2022+
}
2023+
```
2024+
2025+
```js src/Total.js
2026+
const intl = new Intl.NumberFormat("en-US", {
2027+
style: "currency",
2028+
currency: "USD"
2029+
});
2030+
2031+
export default function Total({ clientQuantity, savedQuantity, isPending }) {
2032+
return (
2033+
<div className="total">
2034+
<span>Total:</span>
2035+
<div>
2036+
<div>
2037+
{isPending
2038+
? "🌀 Updating..."
2039+
: `${intl.format(savedQuantity * 9999)}`}
2040+
</div>
2041+
<div className="error">
2042+
{!isPending &&
2043+
clientQuantity !== savedQuantity &&
2044+
`Wrong total, expected: ${intl.format(clientQuantity * 9999)}`}
2045+
</div>
2046+
</div>
2047+
</div>
2048+
);
2049+
}
2050+
```
2051+
2052+
```js src/api.js
2053+
let firstRequest = true;
2054+
export async function updateQuantity(newName) {
2055+
return new Promise((resolve, reject) => {
2056+
if (firstRequest === true) {
2057+
firstRequest = false;
2058+
setTimeout(() => {
2059+
firstRequest = true;
2060+
resolve(newName);
2061+
// Simulate every other request being slower
2062+
}, 1000);
2063+
} else {
2064+
setTimeout(() => {
2065+
resolve(newName);
2066+
}, 50);
2067+
}
2068+
});
2069+
}
2070+
```
2071+
2072+
```css
2073+
.item {
2074+
display: flex;
2075+
align-items: center;
2076+
justify-content: start;
2077+
}
2078+
2079+
.item label {
2080+
flex: 1;
2081+
text-align: right;
2082+
}
2083+
2084+
.item input {
2085+
margin-left: 4px;
2086+
width: 60px;
2087+
padding: 4px;
2088+
}
2089+
2090+
.total {
2091+
height: 50px;
2092+
line-height: 25px;
2093+
display: flex;
2094+
align-content: center;
2095+
justify-content: space-between;
2096+
}
2097+
2098+
.total div {
2099+
display: flex;
2100+
flex-direction: column;
2101+
align-items: flex-end;
2102+
}
2103+
2104+
.error {
2105+
color: red;
2106+
}
2107+
```
2108+
2109+
</Sandpack>

0 commit comments

Comments
 (0)