Skip to content

Commit 059eac0

Browse files
committed
claude feedback
1 parent 438ce33 commit 059eac0

File tree

1 file changed

+46
-45
lines changed

1 file changed

+46
-45
lines changed

src/content/reference/react/useActionState.md

Lines changed: 46 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ title: useActionState
44

55
<Intro>
66

7-
`useActionState` is a React Hook that updates state with side effects using [Actions](/reference/react/useTransition#functions-called-in-starttransition-are-called-actions).
7+
`useActionState` is a React Hook that lets you update state with side effects using [Actions](/reference/react/useTransition#functions-called-in-starttransition-are-called-actions).
88

99
```js
1010
const [state, dispatchAction, isPending] = useActionState(reducerAction, initialState, permalink?);
@@ -51,17 +51,17 @@ function MyCart({initialState}) {
5151
5252
1. The current state. During the first render, it will match the `initialState` you passed. After `dispatchAction` is invoked, it will match the value returned by the `reducerAction`.
5353
2. A `dispatchAction` function that you call inside [Actions](/reference/react/useTransition#functions-called-in-starttransition-are-called-actions).
54-
3. The `isPending` flag that tells you if any dispatched Actions for this hook are pending.
54+
3. The `isPending` flag that tells you if any dispatched Actions for this Hook are pending.
5555
5656
#### Caveats {/*caveats*/}
5757
58-
* `useActionState` is a Hook, so it must be called **at the top level of your component** or your own Hooks. You can't call it inside loops or conditions. If you need that, extract a new component and move the state into it.
58+
* `useActionState` is a Hook, so you can only call it **at the top level of your component** or your own Hooks. You can't call it inside loops or conditions. If you need that, extract a new component and move the state into it.
5959
* React queues and executes multiple calls to `dispatchAction` sequentially, allowing each `reducerAction` to use the result of the previous Action.
6060
* The `dispatchAction` function has a stable identity, so you will often see it omitted from Effect dependencies, but including it will not cause the Effect to fire. If the linter lets you omit a dependency without errors, it is safe to do. [Learn more about removing Effect dependencies.](/learn/removing-effect-dependencies#move-dynamic-objects-and-functions-inside-your-effect)
6161
* When using the `permalink` option, ensure the same form component is rendered on the destination page (including the same `reducerAction` and `permalink`) so React knows how to pass the state through. Once the page becomes interactive, this parameter has no effect.
6262
* When using Server Functions, `initialState` needs to be [serializable](/reference/rsc/use-server#serializable-parameters-and-return-values) (values like plain objects, arrays, strings, and numbers).
6363
* If `dispatchAction` throws an error, React cancels all queued actions and shows the nearest [Error Boundary](/reference/react/Component#catching-rendering-errors-with-an-error-boundary).
64-
* If there are multiple ongoing Actions, React currently batches them together. This is a limitation that may be removed in a future release.
64+
* If there are multiple ongoing Actions, React batches them together. This is a limitation that may be removed in a future release.
6565
6666
<Note>
6767
@@ -86,7 +86,7 @@ async function reducerAction(previousState, actionPayload) {
8686
}
8787
```
8888
89-
Each time you call `dispatchAction`, React calls the `reducerAction` with the `actionPayload`. The reducer will perform side effects such as posting data, and return the new state. If `dispatchAction` is called multiple times, React queues and executes them in order so the result of the previous call is available for current call.
89+
Each time you call `dispatchAction`, React calls the `reducerAction` with the `actionPayload`. The reducer will perform side effects such as posting data, and return the new state. If `dispatchAction` is called multiple times, React queues and executes them in order so the result of the previous call is available for the current call.
9090
9191
#### Parameters {/*reduceraction-parameters*/}
9292
@@ -101,7 +101,7 @@ Each time you call `dispatchAction`, React calls the `reducerAction` with the `a
101101
#### Caveats {/*reduceraction-caveats*/}
102102
103103
* `reducerAction` can be sync or async. It can perform sync actions like showing a notification, or async actions like posting updates to a server.
104-
* `reducerAction` is not invoked twice in StrictMode since `reducerAction` is designed to allow side effects.
104+
* `reducerAction` is not invoked twice in `<StrictMode>` since `reducerAction` is designed to allow side effects.
105105
* The return type of `reducerAction` must match the type of `initialState`. If TypeScript infers a mismatch, you may need to explicitly annotate your state type.
106106
* If you set state after `await` in the `reducerAction` you currently need to wrap the state update in an additional `startTransition`. See the [startTransition](/reference/react/useTransition#react-doesnt-treat-my-state-update-after-await-as-a-transition) docs for more info.
107107
@@ -112,7 +112,7 @@ Each time you call `dispatchAction`, React calls the `reducerAction` with the `a
112112
The function passed to `useActionState` is called a *reducer action* because:
113113
114114
- It *reduces* the previous state into a new state, like `useReducer`.
115-
- It an *Action* because it's called inside a Transition and can perform side effects.
115+
- It's an *Action* because it's called inside a Transition and can perform side effects.
116116
117117
Conceptually, `useActionState` is like `useReducer`, but you can do side effects in the reducer.
118118
@@ -145,13 +145,13 @@ function Counter() {
145145
2. The <CodeStep step={2}>action dispatcher</CodeStep> that lets you trigger `reducerAction`.
146146
3. A <CodeStep step={3}>pending state</CodeStep> that tells you whether the Action is in progress.
147147
148-
To call `addToCartAction`, call the <CodeStep step={2}>action dispatcher</CodeStep>. React will call queue calls to `addToCartAction` with the previous count.
148+
To call `addToCartAction`, call the <CodeStep step={2}>action dispatcher</CodeStep>. React will queue calls to `addToCartAction` with the previous count.
149149
150150
<Sandpack>
151151
152152
```js src/App.js
153153
import { useActionState, startTransition } from 'react';
154-
import { addToCartAction } from './api';
154+
import { addToCart } from './api';
155155
import Total from './Total';
156156

157157
export default function Checkout() {
@@ -200,7 +200,7 @@ export default function Total({quantity, isPending}) {
200200
```
201201
202202
```js src/api.js
203-
export async function addToCartAction(count) {
203+
export async function addToCart(count) {
204204
await new Promise(resolve => setTimeout(resolve, 1000));
205205
return count + 1;
206206
}
@@ -257,16 +257,18 @@ Every time you click "Add Ticket," React queues a call to `addToCartAction`. Rea
257257
258258
#### How `useActionState` queuing works {/*how-useactionstate-queuing-works*/}
259259
260-
Try clicking "Add Ticket" multiple times. Every time you click, a new `addToCart` is queued. Since there's an artificial 1 second delay, that means 4 clicks will take ~4 seconds to complete.
260+
Try clicking "Add Ticket" multiple times. Every time you click, a new `addToCartAction` is queued. Since there's an artificial 1 second delay, that means 4 clicks will take ~4 seconds to complete.
261261
262262
**This is intentional in the design of `useActionState`.**
263263
264-
We have to wait for the previous result of `addToCart` in order to pass the `prevCount` to the next call to `addToCart`. That means React has to wait for the previous Action to finish before calling the next Action.
264+
We have to wait for the previous result of `addToCartAction` in order to pass the `prevCount` to the next call to `addToCartAction`. That means React has to wait for the previous Action to finish before calling the next Action.
265265
266266
You can typically solve this by [using with useOptimistic](/reference/react/useActionState#using-with-useoptimistic) but for more complex cases you may want to consider [cancelling queued actions](#cancelling-queued-actions) or not using `useActionState`.
267267
268268
</DeepDive>
269269
270+
---
271+
270272
### Using multiple Action types {/*using-multiple-action-types*/}
271273
272274
To handle multiple types, you can pass an argument to `dispatchAction`.
@@ -285,13 +287,13 @@ export default function Checkout() {
285287

286288
function handleAdd() {
287289
startTransition(() => {
288-
dispatchAction({type: 'ADD'});
290+
dispatchAction({ type: 'ADD' });
289291
});
290292
}
291293

292294
function handleRemove() {
293295
startTransition(() => {
294-
dispatchAction({type: 'REMOVE'});
296+
dispatchAction({ type: 'REMOVE' });
295297
});
296298
}
297299

@@ -431,7 +433,7 @@ You might notice this example looks a lot like `useReducer`, but they serve diff
431433
432434
- **Use `useActionState`** to manage state of your Actions. The reducer can perform side effects.
433435
434-
You can think of `useActionState` as `useReducer` for side effects from user Actions. Since it computes the next Action to take based on the previous Action, it has to [order the calls sequentially](/reference/react/useActionState#how-useactionstate-queuing-works). If you want to perform Action in parallel, use `useState` and `useTransition` directly.
436+
You can think of `useActionState` as `useReducer` for side effects from user Actions. Since it computes the next Action to take based on the previous Action, it has to [order the calls sequentially](/reference/react/useActionState#how-useactionstate-queuing-works). If you want to perform Actions in parallel, use `useState` and `useTransition` directly.
435437
436438
</DeepDive>
437439
@@ -802,15 +804,15 @@ hr {
802804
</Sandpack>
803805
804806
805-
When the stepper arrow is clicked, `setOptimisticCount` immediately updates the quantity, and `dispatchAction()` queues the `updateCartAction`. We show a pending indicator on both the quantity and total to give the user feedback that their update is still being applied.
807+
When the stepper arrow is clicked, `setOptimisticCount` immediately updates the quantity, and `dispatchAction()` queues the `updateCartAction`. A pending indicator appears on both the quantity and total to give the user feedback that their update is still being applied.
806808
807809
---
808810
809-
### Using with `<form>` action props {/*use-with-a-form*/}
811+
### Using with `<form>` Action props {/*use-with-a-form*/}
810812
811-
The `dispatchAction` function can be passed as the `action` prop to a `<form>`.
813+
You can pass the `dispatchAction` function as the `action` prop to a `<form>`.
812814
813-
When used this way, React automatically wraps the submission in a transition, so you don't need to call `startTransition` yourself. The `reducerAction` receives the previous state and the submitted `FormData`:
815+
When used this way, React automatically wraps the submission in a Transition, so you don't need to call `startTransition` yourself. The `reducerAction` receives the previous state and the submitted `FormData`:
814816
815817
<Sandpack>
816818
@@ -974,7 +976,7 @@ See the [`<form>`](/reference/react-dom/components/form#handle-form-submission-w
974976
975977
### Cancelling queued Actions {/*cancelling-queued-actions*/}
976978
977-
You can use an AbortController pattern to cancel pending Actions:
979+
You can use an `AbortController` pattern to cancel pending Actions:
978980
979981
<Sandpack>
980982
@@ -996,7 +998,7 @@ export default function Checkout() {
996998
}
997999
abortRef.current = new AbortController();
9981000
setOptimisticCount(c => c + 1);
999-
await dispatchAction({type: 'ADD', signal: abortRef.current.signal});
1001+
await dispatchAction({ type: 'ADD', signal: abortRef.current.signal });
10001002
}
10011003

10021004
async function removeAction() {
@@ -1005,7 +1007,7 @@ export default function Checkout() {
10051007
}
10061008
abortRef.current = new AbortController();
10071009
setOptimisticCount(c => Math.max(0, c - 1));
1008-
await dispatchAction({type: 'REMOVE', signal: abortRef.current.signal});
1010+
await dispatchAction({ type: 'REMOVE', signal: abortRef.current.signal });
10091011
}
10101012

10111013
return (
@@ -1025,21 +1027,20 @@ export default function Checkout() {
10251027
);
10261028
}
10271029

1028-
10291030
async function updateCartAction(prevCount, actionPayload) {
10301031
switch (actionPayload.type) {
10311032
case 'ADD': {
10321033
try {
1033-
return await addToCart(prevCount, {signal: actionPayload.signal});
1034+
return await addToCart(prevCount, { signal: actionPayload.signal });
10341035
} catch (e) {
10351036
return prevCount + 1;
10361037
}
10371038
}
10381039
case 'REMOVE': {
10391040
try {
1040-
return await removeFromCart(prevCount, {signal: actionPayload.signal});
1041+
return await removeFromCart(prevCount, { signal: actionPayload.signal });
10411042
} catch (e) {
1042-
return prevCount - 1;
1043+
return Math.max(0, prevCount - 1);
10431044
}
10441045
}
10451046
}
@@ -1097,29 +1098,29 @@ export default function Total({quantity, isPending}) {
10971098
10981099
```js src/api.js hidden
10991100
class AbortError extends Error {
1100-
name = "AbortError";
1101-
constructor(message = "The operation was aborted") {
1101+
name = 'AbortError';
1102+
constructor(message = 'The operation was aborted') {
11021103
super(message);
11031104
}
11041105
}
11051106

1106-
function sleep(ms, signal){
1107+
function sleep(ms, signal) {
11071108
if (!signal) return new Promise((resolve) => setTimeout(resolve, ms));
11081109
if (signal.aborted) return Promise.reject(new AbortError());
11091110

1110-
return new Promise((resolve, reject) => {
1111-
const id = setTimeout(() => {
1112-
signal.removeEventListener("abort", onAbort);
1113-
resolve();
1114-
}, ms);
1115-
1116-
const onAbort = () => {
1117-
clearTimeout(id);
1118-
reject(new AbortError());
1119-
};
1120-
1121-
signal.addEventListener("abort", onAbort, { once: true });
1122-
});
1111+
return new Promise((resolve, reject) => {
1112+
const id = setTimeout(() => {
1113+
signal.removeEventListener('abort', onAbort);
1114+
resolve();
1115+
}, ms);
1116+
1117+
const onAbort = () => {
1118+
clearTimeout(id);
1119+
reject(new AbortError());
1120+
};
1121+
1122+
signal.addEventListener('abort', onAbort, { once: true });
1123+
});
11231124
}
11241125
export async function addToCart(count, opts) {
11251126
await sleep(1000, opts?.signal);
@@ -1196,7 +1197,7 @@ hr {
11961197
11971198
</Sandpack>
11981199
1199-
Try clicking increase or decrease multiple times, and notice that the total updates within 1 second no matter how many times you click. This works because we're using an AbortController to "complete" the previous Action so the next Action can proceed.
1200+
Try clicking increase or decrease multiple times, and notice that the total updates within 1 second no matter how many times you click. This works because it uses an `AbortController` to "complete" the previous Action so the next Action can proceed.
12001201
12011202
<Pitfall>
12021203
@@ -1210,7 +1211,7 @@ For example, if the Action performs a mutation (like writing to a database), abo
12101211
12111212
## Troubleshooting {/*troubleshooting*/}
12121213
1213-
### My action can no longer read the submitted form data {/*action-cant-read-form-data*/}
1214+
### My Action can no longer read the submitted form data {/*action-cant-read-form-data*/}
12141215
12151216
When you use `useActionState`, the `reducerAction` receives an extra argument as its first argument: the previous or initial state. The submitted form data is therefore its second argument instead of its first.
12161217
@@ -1294,7 +1295,7 @@ async function myReducerAction(prevState, data) {
12941295
12951296
---
12961297
1297-
### I want to reset the state {/*reset-state*/}
1298+
### My state doesn't reset {/*reset-state*/}
12981299
12991300
`useActionState` doesn't provide a built-in reset function. To reset the state, you can design your `reducerAction` to handle a reset signal:
13001301

0 commit comments

Comments
 (0)