Useoptimistic

useOptimistic

⚠️ CANARY!

  • 해당 기능은 아직 React의 Canary/Experimental 채널에서만 사용 가능합니다.

📑 Summary

Optimistic: "낙관적인, 앞으로의 일 따위가 잘 되어 갈 것으로 여기는" 이라는 뜻을 가지고 있습니다.

useOptimisticReact Hook으로, 비동기 작업이 진행 중일 때 다른 상태를 보여줄 수 있게 해줍니다.

이 상태는 “낙관적” 상태라고 불리는데, 실제로 작업을 완료하는 데 시간이 걸리더라도 사용자에게 즉시 작업의 결과를 표시하기 위해 일반적으로 사용됩니다.

const [optimisticState, addOptimistic] = useOptimistic(state, (currentState, optimisticValue) => {});

🤔 어떻게 작동 할까요?

일부 상태(state)를 인수로 받아들여 네트워크 요청과 같은 비동기 작업이 진행되는 동안 다룰 수 있는 해당 상태의 복사본을 반환합니다.

현재 상태와 작업에 대한 입력을 가져와 작업이 보류 중인 동안 사용할 낙관적 상태를 적용할 수 있는 방법을 제공합니다.

이를 통해 사용자가 양식을 제출하면 서버의 응답이 변경 사항을 반영할 때까지 기다리는 대신 결과가 즉시 인터페이스로 업데이트됩니다.

Return Values

Return ValuesDetail
optimisticState- 낙관적인 상태의 경우 updateFn에서 반환된 값과 동일합니다. 그렇지 않을 경우 state와 동일합니다.
addOptimistic- 낙관적인 업데이트가 있을 경우 호출하는 디스패치 함수입니다. updateFn의 두 번째 매개변수로 넣어줄 값을 넘겨줄 수 있습니다.

Parameters

ParametersDetail
state- 작업이 진행 중이지 않을 때 반환될 값을 의미합니다..
updateFn(currentState, optimisticValue)- 현재 상태와 addOptimistic에 전달된 낙관적인 값을 취하는 함수로 해당 콜백 함수로 얻을 수 있는 결과 값을 반환합니다.

만약 TodoList를 만든다고 한다면 다음과 같이 만들 수 있습니다.

import { useOptimistic } from 'react';
 
// todoList를 props로 불러와서 사용을 합니다.
function TodoContainer(todoList:TodoType[]) {
  const [optimisticTodoList, optimisticAddTodo] = useOptimistic(
    // 불러온 todoList를 전달해줍니다.
    todoList,
    (prevTodoList, newTodoList) => {
      return [...prevTodoList, newTodoList]
    }
  );
 
  const onSubmit = async (e) => {
    e.preventDefault();
 
    // 낙관적 업데이트를 시도하는 함수
    optimisticAddTodo(newTodo)
 
    // 실제로 데이터를 요청하는 함수
    await addTodo(newTodo)
  }
 
  return (
    <div>
      <ul>
        {optimisticTodoList.map(todo => {
          <li>{...}</li>
        })}
      </ul>
      <form onSubmit={onSubmit}>
        {...}
      </form>
    </div>
  )
}

🧐 실제로는 어떻게 사용할까요?

아래는 Next.js + TypeScript를 이용하여 만든 TodoList입니다.

아래 코드는 최상위 코드로서 todoList를 받아와서 TodoList로 전달해 주는 역할을 하고 있습니다.

// Page
 
export default async function Home() {
  const res = await fetch('http://localhost:3000/apis/todo', {
    cache: 'no-cache',
    next: {
      tags: ['todo'],
    },
  });
 
  const todoList = await res.json();
 
  return (
    <main className="w-full h-screen flex justify-center gap-4 items-center">
      <TodoList todoList={todoList} />
    </main>
  );
}

아래 코드에서는 전달받은 todoListuseOptimistic의 파라미터 값으로 전달해 주었습니다.

또한 콜백 함수를 통해서 addOptimisticAddTodo가 실행되었을 경우 보일 optimisticTodo의 모습을 정의해 주었습니다.

// TodoList
 
'use client';
 
type Props = {
  todoList: TodoType[];
};
 
export default function TodoList({ todoList }: Props) {
  const [optimisticTodo, addOptimisticAddTodo] = useOptimistic<TodoType[], TodoType>(
    todoList,
    (state, newTodo) => [...state, newTodo]
  );
 
  const onSubmit = async (formData: FormData) => {
    const title = formData.get('title')?.toString();
    const content = formData.get('content')?.toString();
 
    if (!title || !content) return console.log('is Empty');
 
    const newTodo: TodoType = {
      content,
      title,
    };
 
    addOptimisticAddTodo(newTodo);
    await addTodo(newTodo);
  };
 
  return (
    ...
  );
}
 

실제 호출 함수인 addTodo가 호출이 된다면 revalidateTag를 통해 다시 todoList를 호출 할 수 있도록 설계해두었습니다.

// addTodo
 
'use server';
 
import { revalidateTag } from 'next/cache';
import { TodoType } from '../components/TodoList/types/todo.type';
 
export const addTodo = async (newTodo: TodoType) => {
  await fetch('http://localhost:3000/apis/todo', {
    method: 'POST',
    cache: 'no-cache',
    body: JSON.stringify(newTodo),
    headers: {
      'Content-type': 'application/json',
    },
  });
 
  revalidateTag('todo');
};

예시를 보여주기 위해서 TodoTypes의 값을 바꿔 진행하겠습니다.

export type TodoType = {
  title: string;
  content: string;
  sending?: boolean;
};
 
export default function TodoList({ todoList }: Props) {
  const [optimisticTodos, addOptimisticTodos] = useOptimistic(todos, (state, newTodo) => [
    ...state,
    {
      ...newTodo,
      // addOptimisticTodos를 통해 값이 업데이트가 될 동안에만 낙관적 업데이트를 통해 sending이 true가 됩니다.
      // 실제 데이터의 경우에는 sending이라는 값 자체가 없습니다.
      sending: true,
    },
  ]);
 
  return (
    <ul>
      {optimisticTodo.map((todo, index) => (
        <li key={'todo' + index}>
          <div>제목: {todo.title}</div>
          <div>할일: {todo.content}</div>
          // 받아온 데이터 중 sending이라는 값이 있을 경우에는 전송 중이라는 문구가 보여지게 됩니다.
          // 낙관적 업데이트 (Data Fetching)를 하고 있을때만 있는 속성 값입니다.
          <div>{todo?.sending && '전송 중'}</div>
        </li>
      ))}
    </ul>
  );
}

🔎 References