You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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
-
87
36
[See more examples below.](#usage)
88
37
89
38
#### Parameters {/*parameters*/}
90
39
91
40
* `state`: The value returned when there are no pending Actions or Transitions.
92
41
* **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.
93
42
94
-
95
43
#### Returns {/*returns*/}
96
44
97
45
`useOptimistic` returns an array with exactly two values:
@@ -121,7 +69,7 @@ function handleClick() {
121
69
#### Parameters {/*setoptimistic-parameters*/}
122
70
123
71
* `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).
125
73
126
74
#### Returns {/*setoptimistic-returns*/}
127
75
@@ -131,6 +79,56 @@ function handleClick() {
131
79
132
80
* 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.
133
81
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:
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
+
134
132
---
135
133
136
134
## Usage {/*usage*/}
@@ -155,7 +153,7 @@ function MyComponent({age, name, todos}) {
155
153
2. The <CodeStep step={3}>set function</CodeStep> that lets you temporarily change the state during an Action or Transition.
156
154
* If a <CodeStep step={4}>reducer</CodeStep> is provided, it will run before rendering the temporary state.
157
155
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:
159
157
160
158
```js [[3, 3, "setOptimisticAge"]]
161
159
functionhandleClick(e) {
@@ -179,17 +177,19 @@ async function submitAction() {
179
177
}
180
178
```
181
179
182
-
This works because Action props are already called inside a Transition.
180
+
This works because Action props are already called inside a Transition.
183
181
184
182
For an example, see: [Using optimistic state in Action props](#using-optimistic-state-in-action-props).
185
183
186
184
</Note>
187
185
186
+
---
187
+
188
188
### Using optimistic state in Action props {/*using-optimistic-state-in-action-props*/}
189
189
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`.
191
191
192
-
This example sets optimistc state inside a `<form>``submitAction` prop:
192
+
This example sets optimistic state inside a `<form>``submitAction` prop:
193
193
194
194
<Sandpack>
195
195
@@ -250,15 +250,17 @@ export async function updateName(name) {
250
250
251
251
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.
252
252
253
-
<Note>
253
+
<DeepDive>
254
254
255
255
#### Why doesn't this need `startTransition`? {/*why-doesnt-this-need-starttransition*/}
256
256
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`.
258
258
259
259
Since you're already in a Transition, calling `setOptimisticName` directly is valid.
260
260
261
-
</Note>
261
+
</DeepDive>
262
+
263
+
---
262
264
263
265
### Adding pending state to a component {/*adding-pending-state-to-a-component*/}
264
266
@@ -319,6 +321,8 @@ export async function submitForm() {
319
321
320
322
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.
321
323
324
+
---
325
+
322
326
### Updating props or state optimistically {/*updating-props-or-state-optimistically*/}
323
327
324
328
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() {
376
380
377
381
</Sandpack>
378
382
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.
380
384
381
385
<Note>
382
386
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.
384
388
385
389
See [Updating state based on the current state](#updating-state-based-on-current-state) for an example.
386
390
387
391
</Note>
388
392
393
+
---
394
+
389
395
### Updating state based on the current state {/*updating-state-based-on-current-state*/}
390
396
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.
392
398
393
399
To calculate the optimistic state relative to the current state, pass an updater function:
394
400
@@ -443,7 +449,7 @@ export async function toggleLike() {
443
449
444
450
</Sandpack>
445
451
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.
447
453
448
454
<DeepDive>
449
455
@@ -475,6 +481,8 @@ Both patterns ensure React can update your optimistic value if the state changes
475
481
476
482
</DeepDive>
477
483
484
+
---
485
+
478
486
### Updating multiple values together {/*updating-multiple-values-together*/}
479
487
480
488
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) {
558
566
559
567
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.
560
568
569
+
---
570
+
561
571
### Optimistically adding to a list {/*optimistically-adding-to-a-list*/}
562
572
563
573
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
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.
644
656
645
657
This shopping cart example shows how to handle add and remove with a single reducer:
646
658
@@ -796,7 +808,9 @@ export async function updateQuantity(id, quantity) {
796
808
797
809
</Sandpack>
798
810
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
+
---
800
814
801
815
### Optimistic delete with error recovery {/*optimistic-delete-with-error-recovery*/}
802
816
@@ -904,36 +918,36 @@ export async function deleteItem(id) {
904
918
905
919
</Sandpack>
906
920
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.
908
922
909
923
---
910
924
911
925
## Troubleshooting {/*troubleshooting*/}
912
926
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*/}
914
928
915
929
You may see this error:
916
930
917
931
<ConsoleBlockMulti>
918
932
919
933
<ConsoleLogLine level="error">
920
934
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`.
922
936
923
937
</ConsoleLogLine>
924
938
925
939
</ConsoleBlockMulti>
926
940
927
-
The optimistic setter function must be called inside a transition:
941
+
The optimistic setter function must be called inside a Transition:
928
942
929
943
```js
930
-
// ❌ Incorrect: outside a transition
944
+
// ❌ Incorrect: outside a Transition
931
945
functionhandleClick() {
932
946
setOptimistic(newValue); // Warning!
933
947
// ...
934
948
}
935
949
936
-
// ✅ Correct: inside a transition
950
+
// ✅ Correct: inside a Transition
937
951
functionhandleClick() {
938
952
startTransition(async () => {
939
953
setOptimistic(newValue);
@@ -948,7 +962,7 @@ function submitAction(formData) {
948
962
}
949
963
```
950
964
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.
952
966
953
967
### I'm getting an error: "Cannot update optimistic state while rendering" {/*cannot-update-optimistic-state-while-rendering*/}
0 commit comments