Skip to content

Commit fbdc328

Browse files
committed
Updates from feedback
1 parent d83be47 commit fbdc328

File tree

1 file changed

+90
-76
lines changed

1 file changed

+90
-76
lines changed

src/content/reference/react/useOptimistic.md

Lines changed: 90 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -18,80 +18,28 @@ const [optimisticState, addOptimistic] = useOptimistic(state, reducer?);
1818
1919
## Reference {/*reference*/}
2020
21-
### `useOptimistic(state, reducer?)` {/*use*/}
21+
### `useOptimistic(state, reducer?)` {/*useoptimistic*/}
2222
2323
Call `useOptimistic` at the top level of your component to declare optimistic state.
2424
25-
26-
2725
```js
2826
import { useOptimistic } from 'react';
2927

3028
function MyComponent({name, todos}) {
3129
const [optimisticAge, setOptimisticAge] = useOptimistic(28);
3230
const [optimisticName, setOptimisticName] = useOptimistic(name);
3331
const [optimisticTodos, setOptimisticTodos] = useOptimistic(todos, todoReducer);
32+
// ...
3433
}
3534
```
3635
37-
<DeepDive>
38-
39-
#### How optimistic state works {/*how-optimistic-state-works*/}
40-
41-
`useOptimistic` lets you show a temporary value while a transition is running:
42-
43-
```js
44-
const [value, setValue] = useState('a');
45-
const [optimistic, setOptimistic] = useOptimistic(value);
46-
47-
startTransition(async () => {
48-
setOptimistic('b');
49-
setValue('b');
50-
});
51-
```
52-
53-
When the setter is called inside a transition, `useOptimistic` will render that value while the transition is in progress. Otherwise, the `value` passed to `useOptimistic` is rendered.
54-
55-
This state is called the "optimistic" state because it is used to immediately present the user with the result of performing an action, even though the action or transition actually takes time to complete.
56-
57-
When the action or transition completes, `useOptimistic` returns the current `value` you passed in.
58-
59-
**How the update flows**
60-
61-
1. **Immediate optimistic update**: When `setOptimistic('b')` is called, `optimistic` immediately becomes `'b'` and React renders with this value.
62-
63-
2. **Transition scheduled**: `setValue('b')` schedules an update to the real state, but this update won't commit until the transition completes.
64-
65-
3. **Optimistic state persists during async work**: If the transition suspends or uses `await`, the optimistic `'b'` continues showing while React waits.
66-
67-
4. **Single render commit**: When all async work finishes, the new state (`'b'`) and optimistic state (`'b'`) commit together in a single render.
68-
69-
There's no extra render to "clear" the optimistic value—the optimistic and real state converge in the same render when the transition completes.
70-
71-
**How the final state is determined**
72-
73-
The `state` argument to `useOptimistic` determines what displays after the transition finishes. How this works depends on the pattern you use:
74-
75-
- **Hardcoded values** like `useOptimistic(false)`: After the transition, `state` is still `false`, so the UI shows `false`. This is useful for pending states where you always start from `false`.
76-
77-
- **Props or state passed in** like `useOptimistic(isLiked)`: If the parent updates `isLiked` during the transition, the new value is used after the transition ends. This is how the UI reflects the result of the action.
78-
79-
- **Reducer pattern** like `useOptimistic(items, fn)`: If `items` changes while the transition is pending, React re-runs your `reducer` with the new `items` to recalculate what to show. This keeps your optimistic additions on top of the latest data.
80-
81-
**What happens when the action fails**
82-
83-
If the Action throws an error, the Transition still ends, and React renders with whatever `state` currently is. Since the parent typically only updates `state` on success, a failure means `state` hasn't changed—so the UI shows what it showed before the optimistic update. You can catch the error to show a message to the user.
84-
85-
</DeepDive>
86-
8736
[See more examples below.](#usage)
8837
8938
#### Parameters {/*parameters*/}
9039
9140
* `state`: The value returned when there are no pending Actions or Transitions.
9241
* **optional** `reducer(currentState, optimisticValue)`: The reducer function that specifies how the optimistic state gets updated. It must be pure, should take the current state and the optimistic value as arguments, and should return the next optimistic state.
9342
94-
9543
#### Returns {/*returns*/}
9644
9745
`useOptimistic` returns an array with exactly two values:
@@ -121,7 +69,7 @@ function handleClick() {
12169
#### Parameters {/*setoptimistic-parameters*/}
12270
12371
* `nextState`: The value that you want the optimistic state to be. If you provided a `reducer` to `useOptimistic`, this value will be passed as the second argument to your reducer. It can be a value of any type.
124-
* If you pass a function as `nextState`, it will be treated as an _updater function_. It must be pure, should take the pending state as its only argument, and should return the next state. React will put your updater function in a queue and re-render your component. During the next render, React will calculate the next state by applying the queued updaters to the previous state similar to [`useState` updaters](/reference/react/useState#setstate-parameters)
72+
* If you pass a function as `nextState`, it will be treated as an _updater function_. It must be pure, should take the pending state as its only argument, and should return the next state. React will put your updater function in a queue and re-render your component. During the next render, React will calculate the next state by applying the queued updaters to the previous state similar to [`useState` updaters](/reference/react/useState#setstate-parameters).
12573
12674
#### Returns {/*setoptimistic-returns*/}
12775
@@ -131,6 +79,56 @@ function handleClick() {
13179
13280
* The `set` function must be called inside a [Transition](/reference/react/useTransition) using [`startTransition`](/reference/react/startTransition) or inside an [Action](/reference/react/useTransition#perform-non-blocking-updates-with-actions). If you call the setter outside an Action or Transition, [React will show a warning](#an-optimistic-state-update-occurred-outside-a-transition-or-action) and the optimistic value will briefly render.
13381
82+
<DeepDive>
83+
84+
#### How optimistic state works {/*how-optimistic-state-works*/}
85+
86+
`useOptimistic` lets you show a temporary value while a Transition is running:
87+
88+
```js
89+
const [value, setValue] = useState('a');
90+
const [optimistic, setOptimistic] = useOptimistic(value);
91+
92+
startTransition(async () => {
93+
setOptimistic('b');
94+
setValue('b');
95+
});
96+
```
97+
98+
When the setter is called inside a Transition, `useOptimistic` will trigger a re-render to show that value while the Transition is in progress. Otherwise, the `value` passed to `useOptimistic` is returned.
99+
100+
This state is called the "optimistic" state because it is used to immediately present the user with the result of performing an Action, even though the Action or Transition actually takes time to complete.
101+
102+
When the Action or Transition completes, `useOptimistic` returns the current `value` you passed in.
103+
104+
**How the update flows**
105+
106+
1. **Immediate optimistic update**: When `setOptimistic('b')` is called, `optimistic` immediately becomes `'b'` and React renders with this value.
107+
108+
2. **Transition scheduled**: `setValue('b')` schedules an update to the real state, but this update won't commit until the Transition completes.
109+
110+
3. **Optimistic state persists during async work**: If the Transition suspends or uses `await`, the optimistic `'b'` continues showing while React waits.
111+
112+
4. **Single render commit**: When all async work finishes, the new state (`'b'`) and optimistic state (`'b'`) commit together in a single render.
113+
114+
There's no extra render to "clear" the optimistic value. The optimistic and real state converge in the same render when the Transition completes.
115+
116+
**How the final state is determined**
117+
118+
The `state` argument to `useOptimistic` determines what displays after the Transition finishes. How this works depends on the pattern you use:
119+
120+
- **Hardcoded values** like `useOptimistic(false)`: After the Transition, `state` is still `false`, so the UI shows `false`. This is useful for pending states where you always start from `false`.
121+
122+
- **Props or state passed in** like `useOptimistic(isLiked)`: If the parent updates `isLiked` during the Transition, the new value is used after the Transition ends. This is how the UI reflects the result of the Action.
123+
124+
- **Reducer pattern** like `useOptimistic(items, fn)`: If `items` changes while the Transition is pending, React re-runs your `reducer` with the new `items` to recalculate what to show. This keeps your optimistic additions on top of the latest data.
125+
126+
**What happens when the Action fails**
127+
128+
If the Action throws an error, the Transition still ends, and React renders with whatever `state` currently is. Since the parent typically only updates `state` on success, a failure means `state` hasn't changed—so the UI shows what it showed before the optimistic update. You can catch the error to show a message to the user.
129+
130+
</DeepDive>
131+
134132
---
135133
136134
## Usage {/*usage*/}
@@ -155,7 +153,7 @@ function MyComponent({age, name, todos}) {
155153
2. The <CodeStep step={3}>set function</CodeStep> that lets you temporarily change the state during an Action or Transition.
156154
* If a <CodeStep step={4}>reducer</CodeStep> is provided, it will run before rendering the temporary state.
157155
158-
To use the optimistic state, call the set function inside a Transition:
156+
To use the optimistic state, call the `set` function inside a Transition:
159157
160158
```js [[3, 3, "setOptimisticAge"]]
161159
function handleClick(e) {
@@ -179,17 +177,19 @@ async function submitAction() {
179177
}
180178
```
181179
182-
This works because Action props are already called inside a Transition.
180+
This works because Action props are already called inside a Transition.
183181
184182
For an example, see: [Using optimistic state in Action props](#using-optimistic-state-in-action-props).
185183
186184
</Note>
187185
186+
---
187+
188188
### Using optimistic state in Action props {/*using-optimistic-state-in-action-props*/}
189189
190-
In an [Action prop](/reference/react/useTransition#exposing-action-props-from-components), you can call the optimistic setter directly without `startTransition`.
190+
In an [Action prop](/reference/react/useTransition#exposing-action-props-from-components), you can call the optimistic setter directly without `startTransition`.
191191
192-
This example sets optimistc state inside a `<form>` `submitAction` prop:
192+
This example sets optimistic state inside a `<form>` `submitAction` prop:
193193
194194
<Sandpack>
195195
@@ -250,15 +250,17 @@ export async function updateName(name) {
250250
251251
In this example, when the user submits the form, the `optimisticName` updates immediately to show the new value while the server request is in progress. When the request completes, the real `name` is updated and the Transition completes rendering the new name.
252252
253-
<Note>
253+
<DeepDive>
254254
255255
#### Why doesn't this need `startTransition`? {/*why-doesnt-this-need-starttransition*/}
256256
257-
The optimistic setter must be called inside a Transition to work correctly. When you pass a function to an [Action prop](/reference/react/useTransition#exposing-action-props-from-components), by convention that function is already called inside `startTransition`.
257+
The optimistic setter must be called inside a Transition to work correctly. When you pass a function to an [Action prop](/reference/react/useTransition#exposing-action-props-from-components), by convention that function is already called inside `startTransition`.
258258
259259
Since you're already in a Transition, calling `setOptimisticName` directly is valid.
260260
261-
</Note>
261+
</DeepDive>
262+
263+
---
262264
263265
### Adding pending state to a component {/*adding-pending-state-to-a-component*/}
264266
@@ -319,6 +321,8 @@ export async function submitForm() {
319321
320322
When the button is clicked, `setIsPending(true)` immediately updates the display to show "Submitting..." and disables the button. When the Action is done, `isPending` is rendered as `false` automatically.
321323
324+
---
325+
322326
### Updating props or state optimistically {/*updating-props-or-state-optimistically*/}
323327
324328
You can wrap props or state in `useOptimistic` to update it immediately while a Transition is in progress.
@@ -376,19 +380,21 @@ export async function toggleLike() {
376380
377381
</Sandpack>
378382
379-
When the button is clicked, `setOptimisticIsLiked` immediately updates the displayed state to show the heart as liked. Meanwhile, `toggleLikeAction` action runs in the background. When the action completes, the parent updates the canonical `isLiked` state, and the optimistic state is rendered to match this new value.
383+
When the button is clicked, `setOptimisticIsLiked` immediately updates the displayed state to show the heart as liked. Meanwhile, `toggleLikeAction` runs in the background. When the Action completes, the parent updates the canonical `isLiked` state, and the optimistic state is rendered to match this new value.
380384
381385
<Note>
382386
383-
This example reads from `optimisticIsLiked` to calculate the next value. This works for simple cases, but if the base state might change while your action is pending, you may want to use a state updater or the reducer.
387+
This example reads from `optimisticIsLiked` to calculate the next value. This works when the base state won't change, but if the base state might change while your Action is pending, you may want to use a state updater or the reducer.
384388
385389
See [Updating state based on the current state](#updating-state-based-on-current-state) for an example.
386390
387391
</Note>
388392
393+
---
394+
389395
### Updating state based on the current state {/*updating-state-based-on-current-state*/}
390396
391-
The [previous example](#updating-props-or-state-optimistically) reads from the current optimistic state, which can cause issues if the base state changes while the action is pending.
397+
The [previous example](#updating-props-or-state-optimistically) reads from the current optimistic state, which can cause issues if the base state changes while the Action is pending.
392398
393399
To calculate the optimistic state relative to the current state, pass an updater function:
394400
@@ -443,7 +449,7 @@ export async function toggleLike() {
443449
444450
</Sandpack>
445451
446-
Here, `liked => !liked` always toggles the latest state. If the base `isLiked` changes during the transition, React will recalculate the optimistic value returned.
452+
Here, `liked => !liked` always toggles the latest state. If the base `isLiked` changes during the Transition, React will recalculate the optimistic value returned.
447453
448454
<DeepDive>
449455
@@ -475,6 +481,8 @@ Both patterns ensure React can update your optimistic value if the state changes
475481
476482
</DeepDive>
477483
484+
---
485+
478486
### Updating multiple values together {/*updating-multiple-values-together*/}
479487
480488
When an optimistic update affects multiple related values, use a reducer to update them together. This ensures the UI stays consistent. Here's a follow button that updates both the follow state and follower count:
@@ -558,6 +566,8 @@ export async function unfollowUser(name) {
558566
559567
The reducer receives the new `isFollowing` value and calculates both the new follow state and the updated follower count in a single update. This ensures the button text and count always stay in sync.
560568
569+
---
570+
561571
### Optimistically adding to a list {/*optimistically-adding-to-a-list*/}
562572
563573
When you need to optimistically add items to a list, use the `reducer` parameter:
@@ -638,9 +648,11 @@ Each optimistic item includes a `pending: true` flag so you can show loading sta
638648
639649
</Note>
640650
641-
### Handling multiple action types {/*handling-multiple-action-types*/}
651+
---
652+
653+
### Handling multiple `action` types {/*handling-multiple-action-types*/}
642654
643-
When you need to handle multiple types of optimistic updates (like adding and removing items), use a reducer pattern with action objects.
655+
When you need to handle multiple types of optimistic updates (like adding and removing items), use a reducer pattern with `action` objects.
644656
645657
This shopping cart example shows how to handle add and remove with a single reducer:
646658
@@ -796,7 +808,9 @@ export async function updateQuantity(id, quantity) {
796808
797809
</Sandpack>
798810
799-
The reducer handles three action types (`add`, `remove`, `update_quantity`) and returns the new optimistic state for each. Each action sets a `pending: true` flag so you can show visual feedback while the server action runs.
811+
The reducer handles three `action` types (`add`, `remove`, `update_quantity`) and returns the new optimistic state for each. Each `action` sets a `pending: true` flag so you can show visual feedback while the [Server Function](/reference/rsc/server-functions) runs.
812+
813+
---
800814
801815
### Optimistic delete with error recovery {/*optimistic-delete-with-error-recovery*/}
802816
@@ -904,36 +918,36 @@ export async function deleteItem(id) {
904918
905919
</Sandpack>
906920
907-
When the delete fails, the optimistic state automatically switches back (since the transition completes), and the item reappears in the list.
921+
When the delete fails, the optimistic state automatically switches back (since the Transition completes), and the item reappears in the list.
908922
909923
---
910924
911925
## Troubleshooting {/*troubleshooting*/}
912926
913-
### I'm getting an error: "An optimistic state update occurred outside a transition or action" {/*an-optimistic-state-update-occurred-outside-a-transition-or-action*/}
927+
### I'm getting an error: "An optimistic state update occurred outside a Transition or Action" {/*an-optimistic-state-update-occurred-outside-a-transition-or-action*/}
914928
915929
You may see this error:
916930
917931
<ConsoleBlockMulti>
918932
919933
<ConsoleLogLine level="error">
920934
921-
An optimistic state update occurred outside a transition or action. To fix, move the update to an action, or wrap with startTransition.
935+
An optimistic state update occurred outside a transition or Action. To fix, move the update to an Action, or wrap with `startTransition`.
922936
923937
</ConsoleLogLine>
924938
925939
</ConsoleBlockMulti>
926940
927-
The optimistic setter function must be called inside a transition:
941+
The optimistic setter function must be called inside a Transition:
928942
929943
```js
930-
// ❌ Incorrect: outside a transition
944+
// ❌ Incorrect: outside a Transition
931945
function handleClick() {
932946
setOptimistic(newValue); // Warning!
933947
// ...
934948
}
935949

936-
// ✅ Correct: inside a transition
950+
// ✅ Correct: inside a Transition
937951
function handleClick() {
938952
startTransition(async () => {
939953
setOptimistic(newValue);
@@ -948,7 +962,7 @@ function submitAction(formData) {
948962
}
949963
```
950964
951-
When you call the setter outside a transition, the optimistic value will briefly appear and then immediately revert back to the original value. This happens because there's no transition to "hold" the optimistic state while your action runs.
965+
When you call the setter outside a Transition, the optimistic value will briefly appear and then immediately revert back to the original value. This happens because there's no Transition to "hold" the optimistic state while your Action runs.
952966
953967
### I'm getting an error: "Cannot update optimistic state while rendering" {/*cannot-update-optimistic-state-while-rendering*/}
954968

0 commit comments

Comments
 (0)