✨ useOptimistic
⚠️ CANARY!
- 해당 기능은 아직 React의 Canary/Experimental 채널에서만 사용 가능합니다.
📑 Summary
Optimistic
: "낙관적인, 앞으로의 일 따위가 잘 되어 갈 것으로 여기는" 이라는 뜻을 가지고 있습니다.
useOptimistic
은 React Hook
으로, 비동기 작업이 진행 중일 때 다른 상태를 보여줄 수 있게 해줍니다.
이 상태는 “낙관적” 상태라고 불리는데, 실제로 작업을 완료하는 데 시간이 걸리더라도 사용자에게 즉시 작업의 결과를 표시하기 위해 일반적으로 사용됩니다.
const [optimisticState, addOptimistic] = useOptimistic(state, (currentState, optimisticValue) => {});
🤔 어떻게 작동 할까요?
일부 상태(state)를 인수로 받아들여 네트워크 요청과 같은 비동기 작업이 진행되는 동안 다룰 수 있는 해당 상태의 복사본을 반환합니다.
현재 상태와 작업에 대한 입력을 가져와 작업이 보류 중인 동안 사용할 낙관적 상태를 적용할 수 있는 방법을 제공합니다.
이를 통해 사용자가 양식을 제출하면 서버의 응답이 변경 사항을 반영할 때까지 기다리는 대신 결과가 즉시 인터페이스로 업데이트됩니다.
Return Values
Return Values | Detail |
---|---|
optimisticState | - 낙관적인 상태의 경우 updateFn 에서 반환된 값과 동일합니다. 그렇지 않을 경우 state 와 동일합니다. |
addOptimistic | - 낙관적인 업데이트가 있을 경우 호출하는 디스패치 함수입니다. updateFn의 두 번째 매개변수로 넣어줄 값을 넘겨줄 수 있습니다. |
Parameters
Parameters | Detail |
---|---|
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>
);
}
아래 코드에서는 전달받은 todoList
를 useOptimistic
의 파라미터 값으로 전달해 주었습니다.
또한 콜백 함수를 통해서 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>
);
}