주로 서버의 데이터를 요청하는 GET 메서드의 경우 useQuery, useQueries를 사용한다.
하지만, 서버의 데이터를 바꾸는 POST, PUT, DELETE 메서드에 대해서는 useMutation을 사용하는 것을 권장한다.
이 때, onSuccess, onError와 같은 옵션을 사용해 데이터 변경후 처리를 커스텀하게 개발할 수 있다.
[ 데이터 변경 후 UI 적용 방법 ]
- Invalidate Queries
- 업데이트 된 데이터만 추가 / 수정 / 삭제
- Optimistic Update (낙관적 업데이트)
특정 쿼리키를 무효화시켜 데이터를 리패칭시키는 방법이다.
[ 예시 ]
//todos.js
const getTodos = async () => {
const { data } = await axios.get("http://localhost:5000/todos");
return data;
};
const addTodo = async (todo) => {
const { data } = await axios.post("http://localhost:5000/todos", {
todo,
done: false,
});
return data;
};
const TodosPage = () => {
const [todo, setTodo] = useState("");
const queryClient = useQueryClient();
const {
data: todos,
isLoading,
isError,
error,
} = useQuery("todos", getTodos, {refetchOnWindowFocus: false});
const { mutate } = useMutation(addTodo, {
//성공시 쿼리 무효화
onSuccess: () => {
queryClient.invalidateQueries("todos");
},
});
const onSubmit = useCallback(
(e) => {
e.preventDefault();
mutate(todo);
setTodo("");
},
[mutate, todo]
);
if (isError) {
return <div>{error.message}</div>;
}
return (
<>
<form onSubmit={onSubmit}>
<label>할 일: </label>
<input
type="text"
value={todo}
onChange={(e) =>
setTodo(e.target.value)
}
/>
<button type="submit">작성</button>
</form>
<br />
<div>
{isLoading ? (
<div>Loading...</div>
) : (
todos?.map((todo) => (
<Fragment key={todo.id}>
<div>ID: {todo.id}</div>
<div>할 일: {todo.todo}</div>
<br />
<hr />
</Fragment>
))
)}
</div>
</>
);
};
export default TodosPage;
[ 결과 ]
입력과 동시에 데이터가 리패칭 되어 아래에 나난다.
addTodo -> 성공 -> todos 쿼리 무효화 -> 데이터 리패치
Invalidate Queries 쿼리를 통해 데이터를 리패치 할 경우 이전 데이터를 전부 무효화 시키기 때문에 네트워크 리소스가 낭비될 수 있다.
추가한 데이터만 서버측에서 응답으로 주고, 그것을 화면에 그리면 GET 요청을 하지 않아도 된다.
쿼리 무효화 없이 변경된 데이터만 추가 / 수정 / 삭제 해주는 방법이다.
[ 예시 ]
const { mutate } = useMutation(addTodo, {
onSuccess: (data) => {
// queryClient.invalidateQueries("todos");
queryClient.setQueryData("todos", (oldData) => {
if (!oldData) {
return [];
}
return [...oldData, { id: data.id, todo: data.todo, done: false }];
});
},
});
onSuccess()의 data는 서버에서 응답해준 값이다.
oldData는 todos쿼리가 가지고 있던 기존의 데이터이다. 데이터 불변성을 지키기 위해 ...oldData를 해주어야 한다.
[ 결과 ]
쿼리 무효화와는 다르게 네트워크 낭비 없이 데이터를 추가했다.
이 방법을 사용할 경우에는 서버의 response 데이터가 필요하다.
위의 두가지 방법은 서버 통신(POST) 후 onSuccess일 경우에만 데이터를 바꾸어주는 경우이다.
이와 반대로 Optimistic Update는 POST 요청 직후 데이터를 바꾸어주고 실패했을 경우 onError, onSettled를 활용해 롤백 시킨다.
[ 예시 ]
const { mutate } = useMutation(addTodo, {
// ** 1
onMutate: async (newTodo) => {
// ** 2
await queryClient.cancelQueries("todos");
const previousTodos = queryClient.getQueryData("todos");
queryClient.setQueryData("todos", (oldData) => {
if (!oldData) {
return [];
}
return [
...oldData,
{ id: oldData.length + 1, todo: newTodo, done: false },
];
});
return { previousTodos };
},
// ** 3
onError: (_error, _newTodo, context) => {
queryClient.setQueryData("todos", context?.previousTodos);
},
// ** 4
onSettled: () => {
queryClient.invalidateQueries("todos");
},
});
[ 결과 ]
요청 즉시 화면을 업데이트 시키기 때문에 사용자 경험이 향상될 수 있다.
하지만 추가 작업이 필요하기 때문에 본인은 사용하지 않는다.