주로 서버의 데이터를 요청하는 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");
    },
  });
[ 결과 ]
요청 즉시 화면을 업데이트 시키기 때문에 사용자 경험이 향상될 수 있다.
하지만 추가 작업이 필요하기 때문에 본인은 사용하지 않는다.