I introduce "useOptimistic" which is new hook in React19.
Note: As of July 2024, this hook is Canary version. Hence, it does not ensure it is stable so hesitate to use in production environment.
What is useOptimistic in React?
useOptimistic is a React hook that displays a different state only while some asynchronous action is in progress.
A use case for useOptimistic is when you want the UI to update before the state in the database is updated. This is often used in message App. For example, when a user sends a comment, it is processed by the database. During this processing, the UI updates to display the comment. If there is an error in the database processing, the comment is removed (reverted to the state before the processing).
Example of message App by using useOptimistic.
Let's consider the previous example.
①When a user makes a comment and clicks the send button, a form action is executed.
For those unfamiliar with form actions, please read this article first.
②The state of useOptimistic is updated by the function in useOptimistic.
④Finish the Form Action and copy original state
In this way, useOptimistic can hold a temporary state and reflect it in the UI.
How to use it.
const [optimisticState, addOptimistic] = useOptimistic(
originalState,
// updateFn
(currentState, optimisticValue) => {
// merge and return new state
// with optimistic value
}
);
optimisticState: This is a state that holds temporary information and, except during server action processing, it will have the same value as the original state.
addOptimistic: This is a function called to update the optimisticState. It is called during server action processing and and allows for passing arguments at that time.
originalState: This is the original state. This value is expected to be displayed in the initial state and outside of server action execution.
updateFn(currentState, optimisticValue): This function receives the current state and the arguments passed to addOptimistic, and performs the update process for optimisticState.
Note:
Calling addOptimistic within a server action triggers UseOptimistic. At that time, arguments can be passed.
//Server Action
const formAction = async (formData: FormData) => {
addOptimistic(formData.get("message")); //update useOptimistic state
formRef.current!.reset();
//Assumed server-side processing(Data based)
await sendMessage(formData); //update original state
};
Example of the codes
page.tsx
"use client";
import UseOptimistic from "././components/UseOptimistic";
import { useState } from "react";
export default function Home() {
//useState(original state)
const [originalState, setOriginalState] = useState([
{ text: "Hello there!", sending: false, key: 1 },
]);
//function update original state
const sendMessage = async (formData: FormData) => {
await new Promise((res) => setTimeout(res, 2000));
const sentMessage = formData.get("message") as string;
setOriginalState((originalState) => [
...originalState,
{ text: sentMessage, sending: false, key: originalState.length + 1 },
]);
};
return (
<UseOptimistic originalState={originalState} sendMessage={sendMessage} />
);
}
In this file, I have a original state and function to update original state. And I pass them to the component of UseOptimistic.tsx as props.
UseOptimistic.tsx
import { useOptimistic, useRef } from "react";
interface ThreadProps {
originalState: { text: string; sending: boolean; key: number }[];
sendMessage: (formData: FormData) => void;
}
const UseOptimistic = ({ originalState, sendMessage }: ThreadProps) => {
const formRef = useRef<HTMLFormElement>(null);
//Server Action
const formAction = async (formData: FormData) => {
addOptimistic(formData.get("message")); //update useOptimistic state
formRef.current!.reset();
//Assumed server-side processing(Data based)
await sendMessage(formData); //update original state
};
const [optimisticState, addOptimistic] = useOptimistic(
originalState,
(currentState, optimisticValue) => [
...currentState,
{
text: optimisticValue as string,
sending: true,
key: currentState.length + 1,
},
]
);
return (
<>
{optimisticState.map((message, index) => (
<div key={index}>
{message.text}
{!!message.sending && <small> (Sending...)</small>}
</div>
))}
<form action={formAction} ref={formRef}>
<input type="text" name="message" placeholder="Hello!" />
<button type="submit">Send</button>
</form>
</>
);
};
export default UseOptimistic;
In this file, I have a optimisticState and function to update this state by using useOptimistic. And I have a server action(form action). When user click the button, formAction is called and update the optimisticState. During the this operation, we can see the new text with "Sending". After execution of formAction, optimisticState has original state. Hence, we can not see "Sending". However, In form action, I call the sendMessage and update original state so I can see new text without "sending".
Top comments (0)