useOptimistic

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은 두 개의 매개변수를 취합니다. currentStateoptimisticValue. 반환 값은 currentStateoptimisticValue의 병합된 값입니다.

반환값

  • optimisticState: 결과적인 낙관적인 상태입니다. 작업이 대기 중이지 않을 때는 state와 동일하며, 그렇지 않은 경우 updateFn에서 반환된 값과 동일합니다.
  • addOptimistic: addOptimistic는 낙관적인 업데이트가 있을 때 호출하는 dispatch 함수입니다. 어떠한 타입의 optimisticValue라는 하나의 인자를 취하며, stateoptimisticValueupdateFn을 호출합니다.

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 a reducer to useOptimistic, 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 to useState updaters.

Returns

set functions do not have a return value.

Caveats

  • The set function 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.
자세히 살펴보기

How optimistic state works

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

  1. Update immediately: When setOptimistic('b') is called, React immediately renders with 'b'.

  2. (Optional) await in Action: If you await in the Action, React continues showing 'b'.

  3. Transition scheduled: setValue(newValue) schedules an update to the real state.

  4. (Optional) wait for Suspense: If newValue suspends, React continues showing 'b'.

  5. Single render commit: Finally, the newValue commits for value and optimistic.

There’s no extra render to “clear” the optimistic state. The optimistic and real state converge in the same render when the Transition completes.

중요합니다!

Optimistic state is temporary

Optimistic state only renders while an Action is in progress, otherwise value is rendered.

If saveChanges returned 'c', then both value and optimistic will be 'c', not 'b'.

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, state is still false, so the UI shows false. This is useful for pending states where you always start from false.

  • Props or state passed in like useOptimistic(isLiked): If the parent updates isLiked during 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): If items changes while the Action is pending, React re-runs your reducer with the new items to 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:

  1. The optimistic state, initially set to the value provided.
  2. 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.

중요합니다!

When using Action props, you can call the set function without startTransition:

async function submitAction() {
setOptimisticName('Taylor');
await updateName('Taylor');
}

This works because Action props are already called inside startTransition.

For an example, see: Using optimistic state in Action props.


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.

자세히 살펴보기

Why doesn’t this need startTransition?

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 안의 모든 작업이 끝날 때까지 보류 상태가 표시됩니다.

중요합니다!

useTransition을 사용하여 isPending으로 보류 상태를 가져올 수도 있습니다.

차이점은 useTransitionstartTransition 함수를 제공하는 반면, useOptimistic은 모든 Transition과 함께 동작한다는 것입니다. 컴포넌트의 필요에 맞는 것을 사용하세요.


props나 state를 낙관적으로 업데이트하기

props나 state를 useOptimistic으로 감싸 Action이 진행 중일 때 즉시 업데이트할 수 있습니다.

이 예시에서 LikeButtonisLiked를 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가 완료되면 상위의