useOptimistic은 UI를 낙관적으로 업데이트할 수 있게 해주는 React Hook입니다.
const [optimisticState, setOptimistic] = useOptimistic(value, reducer?);레퍼런스
useOptimistic(value, reducer?)
useOptimistic은 React Hook으로, 비동기 작업이 진행 중일 때 다른 상태를 보여줄 수 있게 해줍니다. 인자로 주어진 일부 상태를 받아, 네트워크 요청과 같은 비동기 작업 기간 동안 달라질 수 있는 그 상태의 복사본을 반환합니다. 현재 상태와 작업의 입력을 취하는 함수를 제공하고, 작업이 대기 중일 때 사용할 낙관적인 상태를 반환합니다.
이 상태는 “낙관적” 상태라고 불리는데, 실제로 작업을 완료하는 데 시간이 걸리더라도 사용자에게 즉시 작업의 결과를 표시하기 위해 일반적으로 사용됩니다.
import { useOptimistic } from 'react';
function MyComponent({name, todos}) {
const [optimisticAge, setOptimisticAge] = useOptimistic(28);
const [optimisticName, setOptimisticName] = useOptimistic(name);
const [optimisticTodos, setOptimisticTodos] = useOptimistic(todos, todoReducer);
// ...
}매개변수
state: 작업이 대기 중이지 않을 때 초기에 반환될 값입니다.updateFn(currentState, optimisticValue): 현재 state와addOptimistic에 전달된 낙관적인 값을 취하는 함수로, 결과적인 낙관적인 state를 반환합니다. 순수 함수여야 합니다.updateFn은 두 개의 매개변수를 취합니다.currentState와optimisticValue. 반환 값은currentState와optimisticValue의 병합된 값입니다.
반환값
optimisticState: 결과적인 낙관적인 상태입니다. 작업이 대기 중이지 않을 때는state와 동일하며, 그렇지 않은 경우updateFn에서 반환된 값과 동일합니다.addOptimistic:addOptimistic는 낙관적인 업데이트가 있을 때 호출하는 dispatch 함수입니다. 어떠한 타입의optimisticValue라는 하나의 인자를 취하며,state와optimisticValue로updateFn을 호출합니다.
set functions, like setOptimistic(optimisticState)
The set function returned by useOptimistic lets you update the state for the duration of an Action. You can pass the next state directly, or a function that calculates it from the previous state:
const [optimisticLike, setOptimisticLike] = useOptimistic(false);
const [optimisticSubs, setOptimisticSubs] = useOptimistic(subs);
function handleClick() {
startTransition(async () => {
setOptimisticLike(true);
setOptimisticSubs(a => a + 1);
await saveChanges();
});
}Parameters
optimisticState: The value that you want the optimistic state to be during an Action. If you provided areducertouseOptimistic, this value will be passed as the second argument to your reducer. It can be a value of any type.- If you pass a function as
optimisticState, 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 optimistic 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 touseStateupdaters.
- If you pass a function as
Returns
set functions do not have a return value.
Caveats
- The
setfunction must be called inside an Action. If you call the setter outside an Action, React will show a warning and the optimistic state will briefly render.
자세히 살펴보기
useOptimistic lets you show a temporary value while an Action is in progress:
const [value, setValue] = useState('a');
const [optimistic, setOptimistic] = useOptimistic(value);
startTransition(async () => {
setOptimistic('b');
const newValue = await saveChanges('b');
setValue(newValue);
});When the setter is called inside an Action, useOptimistic will trigger a re-render to show that state while the Action is in progress. Otherwise, the value passed to useOptimistic is returned.
This state is called the “optimistic” because it is used to immediately present the user with the result of performing an Action, even though the Action actually takes time to complete.
How the update flows
-
Update immediately: When
setOptimistic('b')is called, React immediately renders with'b'. -
(Optional) await in Action: If you await in the Action, React continues showing
'b'. -
Transition scheduled:
setValue(newValue)schedules an update to the real state. -
(Optional) wait for Suspense: If
newValuesuspends, React continues showing'b'. -
Single render commit: Finally, the
newValuecommits forvalueandoptimistic.
There’s no extra render to “clear” the optimistic state. The optimistic and real state converge in the same render when the Transition completes.
How the final state is determined
The value argument to useOptimistic determines what displays after the Action finishes. How this works depends on the pattern you use:
-
Hardcoded values like
useOptimistic(false): After the Action,stateis stillfalse, so the UI showsfalse. This is useful for pending states where you always start fromfalse. -
Props or state passed in like
useOptimistic(isLiked): If the parent updatesisLikedduring the Action, the new value is used after the Action completes. This is how the UI reflects the result of the Action. -
Reducer pattern like
useOptimistic(items, fn): Ifitemschanges while the Action is pending, React re-runs yourreducerwith the newitemsto recalculate the state. This keeps your optimistic additions on top of the latest data.
What happens when the Action fails
If the Action throws an error, the Transition still ends, and React renders with whatever value currently is. Since the parent typically only updates value on success, a failure means value 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.
사용법
Adding optimistic state to a component
Call useOptimistic at the top level of your component to declare one or more optimistic states.
import { useOptimistic } from 'react';
function MyComponent({age, name, todos}) {
const [optimisticAge, setOptimisticAge] = useOptimistic(age);
const [optimisticName, setOptimisticName] = useOptimistic(name);
const [optimisticTodos, setOptimisticTodos] = useOptimistic(todos, reducer);
// ...useOptimistic returns an array with exactly two items:
- The optimistic state, initially set to the value provided.
- The set function that lets you temporarily change the state during an Action.
- If a reducer is provided, it will run before returning the optimistic state.
To use the optimistic state, call the set function inside an Action.
Actions are functions called inside startTransition:
function onAgeChange(e) {
startTransition(async () => {
setOptimisticAge(42);
const newAge = await postAge(42);
setAge(newAge);
});
}React will render the optimistic state 42 first while the age remains the current age. The Action waits for POST, and then renders the newAge for both age and optimisticAge.
See How optimistic state works for a deep dive.
Using optimistic state in Action props
In an Action prop, you can call the optimistic setter directly without startTransition.
This example sets optimistic state inside a <form> submitAction prop:
import { useOptimistic, startTransition } from 'react'; import { updateName } from './actions.js'; export default function EditName({ name, action }) { const [optimisticName, setOptimisticName] = useOptimistic(name); async function submitAction(formData) { const newName = formData.get('name'); setOptimisticName(newName); const updatedName = await updateName(newName); startTransition(() => { action(updatedName); }) } return ( <form action={submitAction}> <p>Your name is: {optimisticName}</p> <p> <label>Change it: </label> <input type="text" name="name" disabled={name !== optimisticName} /> </p> </form> ); }
In this example, when the user submits the form, the optimisticName updates immediately to show the newName optimistically while the server request is in progress. When the request completes, name and optimisticName are rendered with the actual updatedName from the response.
자세히 살펴보기
By convention, props called inside startTransition are named with “Action”.
Since submitAction is named with “Action”, you know it’s already called inside startTransition.
See Exposing action prop from components for the Action prop pattern.
Adding optimistic state to Action props
When creating an Action prop, you can add useOptimistic to show immediate feedback.
Here’s a button that shows “Submitting…” while the action is pending:
import { useOptimistic, startTransition } from 'react'; export default function Button({ action, children }) { const [isPending, setIsPending] = useOptimistic(false); return ( <button disabled={isPending} onClick={() => { startTransition(async () => { setIsPending(true); await action(); }); }} > {isPending ? 'Submitting...' : children} </button> ); }
버튼을 클릭하면 setIsPending(true)가 낙관적 state를 사용하여 즉시 “Submitting…”을 표시하고 버튼을 비활성화합니다. Action이 완료되면 isPending은 자동으로 false로 렌더링됩니다.
이 패턴은 Button에서 action prop을 어떤 방식으로 사용하든 보류 상태를 자동으로 표시합니다.
// state 업데이트에 대한 보류 상태 표시
<Button action={() => { setState(c => c + 1) }} />
// 네비게이션에 대한 보류 상태 표시
<Button action={() => { navigate('/done') }} />
// POST에 대한 보류 상태 표시
<Button action={async () => { await fetch(/* ... */) }} />
// 모든 조합에 대한 보류 상태 표시
<Button action={async () => {
setState(c => c + 1);
await fetch(/* ... */);
navigate('/done');
}} />action prop 안의 모든 작업이 끝날 때까지 보류 상태가 표시됩니다.
props나 state를 낙관적으로 업데이트하기
props나 state를 useOptimistic으로 감싸 Action이 진행 중일 때 즉시 업데이트할 수 있습니다.
이 예시에서 LikeButton은 isLiked를 prop으로 받고, 클릭하면 즉시 토글합니다.
import { useState, useOptimistic, startTransition } from 'react'; import { toggleLike } from './actions.js'; export default function App() { const [isLiked, setIsLiked] = useState(false); const [optimisticIsLiked, setOptimisticIsLiked] = useOptimistic(isLiked); function handleClick() { startTransition(async () => { const newValue = !optimisticIsLiked console.log('⏳ setting optimistic state: ' + newValue); setOptimisticIsLiked(newValue); const updatedValue = await toggleLike(newValue); startTransition(() => { console.log('⏳ setting real state: ' + updatedValue ); setIsLiked(updatedValue); }); }); } if (optimisticIsLiked !== isLiked) { console.log('✅ rendering optimistic state: ' + optimisticIsLiked); } else { console.log('✅ rendering real value: ' + optimisticIsLiked); } return ( <button onClick={handleClick}> {optimisticIsLiked ? '❤️ Unlike' : '🤍 Like'} </button> ); }
버튼을 클릭하면 setOptimisticIsLiked가 표시되는 state를 즉시 업데이트하여 하트가 좋아요 상태로 보이게 합니다. 그동안 await toggleLike는 백그라운드에서 실행됩니다. await가 완료되면 상위의