Skip to content

Commit 0ebfbff

Browse files
committed
Add example of useActionState handling execution order
1 parent 5598696 commit 0ebfbff

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

0 commit comments

Comments
 (0)