Skip to content

Commit a3e9466

Browse files
authored
fix action pattern in useTransition / useOptimistic (#7796)
* fix action pattern in useOptimistic * update useTransition too
1 parent d6c4c0f commit a3e9466

File tree

2 files changed

+39
-24
lines changed

2 files changed

+39
-24
lines changed

src/content/reference/react/useOptimistic.md

+7-7
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,9 @@ function Thread({ messages, sendMessageAction }) {
7474
function formAction(formData) {
7575
addOptimisticMessage(formData.get("message"));
7676
formRef.current.reset();
77-
sendMessageAction(formData);
77+
startTransition(async () => {
78+
await sendMessageAction(formData);
79+
});
7880
}
7981
const [optimisticMessages, addOptimisticMessage] = useOptimistic(
8082
messages,
@@ -108,12 +110,10 @@ export default function App() {
108110
const [messages, setMessages] = useState([
109111
{ text: "Hello there!", sending: false, key: 1 }
110112
]);
111-
function sendMessageAction(formData) {
112-
startTransition(async () => {
113-
const sentMessage = await deliverMessage(formData.get("message"));
114-
startTransition(() => {
115-
setMessages((messages) => [{ text: sentMessage }, ...messages]);
116-
})
113+
async function sendMessageAction(formData) {
114+
const sentMessage = await deliverMessage(formData.get("message"));
115+
startTransition(() => {
116+
setMessages((messages) => [{ text: sentMessage }, ...messages]);
117117
})
118118
}
119119
return <Thread messages={messages} sendMessageAction={sendMessageAction} />;

src/content/reference/react/useTransition.md

+32-17
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,8 @@ function SubmitButton({ submitAction }) {
7777
<button
7878
disabled={isPending}
7979
onClick={() => {
80-
startTransition(() => {
81-
submitAction();
80+
startTransition(async () => {
81+
await submitAction();
8282
});
8383
}}
8484
>
@@ -227,9 +227,9 @@ import { startTransition } from "react";
227227

228228
export default function Item({action}) {
229229
function handleChange(event) {
230-
// To expose an action prop, call the callback in startTransition.
230+
// To expose an action prop, await the callback in startTransition.
231231
startTransition(async () => {
232-
action(event.target.value);
232+
await action(event.target.value);
233233
})
234234
}
235235
return (
@@ -585,19 +585,20 @@ This solution makes the app feel slow, because the user must wait each time they
585585

586586
You can expose an `action` prop from a component to allow a parent to call an Action.
587587

588-
589588
For example, this `TabButton` component wraps its `onClick` logic in an `action` prop:
590589

591-
```js {8-10}
590+
```js {8-12}
592591
export default function TabButton({ action, children, isActive }) {
593592
const [isPending, startTransition] = useTransition();
594593
if (isActive) {
595594
return <b>{children}</b>
596595
}
597596
return (
598597
<button onClick={() => {
599-
startTransition(() => {
600-
action();
598+
startTransition(async () => {
599+
// await the action that's passed in.
600+
// This allows it to be either sync or async.
601+
await action();
601602
});
602603
}}>
603604
{children}
@@ -656,10 +657,15 @@ export default function TabButton({ action, children, isActive }) {
656657
if (isActive) {
657658
return <b>{children}</b>
658659
}
660+
if (isPending) {
661+
return <b className="pending">{children}</b>;
662+
}
659663
return (
660-
<button onClick={() => {
661-
startTransition(() => {
662-
action();
664+
<button onClick={async () => {
665+
startTransition(async () => {
666+
// await the action that's passed in.
667+
// This allows it to be either sync or async.
668+
await action();
663669
});
664670
}}>
665671
{children}
@@ -729,10 +735,19 @@ export default function ContactTab() {
729735
```css
730736
button { margin-right: 10px }
731737
b { display: inline-block; margin-right: 10px; }
738+
.pending { color: #777; }
732739
```
733740

734741
</Sandpack>
735742

743+
<Note>
744+
745+
When exposing an `action` prop from a component, you should `await` it inside the transition.
746+
747+
This allows the `action` callback to be either synchronous or asynchronous without requiring an additional `startTransition` to wrap the `await` in the action.
748+
749+
</Note>
750+
736751
---
737752

738753
### Displaying a pending visual state {/*displaying-a-pending-visual-state*/}
@@ -804,8 +819,8 @@ export default function TabButton({ action, children, isActive }) {
804819
}
805820
return (
806821
<button onClick={() => {
807-
startTransition(() => {
808-
action();
822+
startTransition(async () => {
823+
await action();
809824
});
810825
}}>
811826
{children}
@@ -1095,8 +1110,8 @@ export default function TabButton({ action, children, isActive }) {
10951110
}
10961111
return (
10971112
<button onClick={() => {
1098-
startTransition(() => {
1099-
action();
1113+
startTransition(async () => {
1114+
await action();
11001115
});
11011116
}}>
11021117
{children}
@@ -1822,8 +1837,8 @@ import {startTransition} from 'react';
18221837
export default function Item({action}) {
18231838
function handleChange(e) {
18241839
// Update the quantity in an Action.
1825-
startTransition(() => {
1826-
action(e.target.value);
1840+
startTransition(async () => {
1841+
await action(e.target.value);
18271842
});
18281843
}
18291844
return (

0 commit comments

Comments
 (0)