[React] React Query 비동기 작업 처리하기 (useEffect → useQuery → useMutation)

suno·2022년 12월 28일
3
post-thumbnail
post-custom-banner

todo list

To Do List 프로젝트 진행 중 서버에서 Todo 데이터를 받아오는 과정을 3단계에 걸쳐 개선했다.
useEffectuseQueryuseMutation 순으로 변경된 과정과 코드를 정리한 문서이다. 🙂


1. useEffect

홈페이지인 MyTodoList 컴포넌트에 두 개의 TodoList가 컴포넌트가 들어간다.
각각의 TodoList 컴포넌트가 렌더링 될 때, useEffect 내부에 있는 get 요청이 두번 실행된다.

문제

  • 서버에 중복 요청을 하게 되어 비효율적이다.
  • initializeTodos 함수 또한 두번 실행되므로 데이터 중복 문제가 발생한다.
// pages/MyTodoList.jsx

export default function MyTodoList() {
  return (
    <>
      <AddForm />
      {/* active/done 투두리스트 */}
      <Container className='todo-lists'>
        <TodoList name='active' />
        <TodoList name='done' />
      </Container>
    </>
  );
}
// components/TodoList.jsx

export default function TodoList({ name }) {
  const isActiveList = name === 'active' ? true : false;
  const todos = useSelector((state) => state.todos);
  const dispatch = useDispatch();

  useEffect(() => {
    axios.get(`${SERVER_URL}/todos`).then((res) => {
      dispatch(initializeTodos(res.data));
    });
  }, []);

  return (
    <styled.Container>
      {/* active/done 리스트에 따라 타이틀 표시 */}
      <styled.Title>{isActiveList ? 'Active 🔥' : 'Done ✅'}</styled.Title>
      {todos
        // active일 때는 isDone이 false인 값만 표시
        // done일 때는 inDone이 true인 값만 표시
        .filter((t) => isActiveList === !t.isDone)
        .map((t) => (
          <Todo todo={t.todo} isDone={t.isDone} key={t.id} id={t.id} />
        ))}
    </styled.Container>
  );
}

2. useQuery

  • queryKey로 데이터를 구분하고, 중복 요청을 처리할 수 있다.
    • stale time (데이터를 캐싱할 시간)을 지정할 수 있다.
    • 기본 옵션은 요청 즉시 데이터가 stale하다고 판단한다. 따라서 캐싱을 원한다면 staleTime이라는 옵션을 지정해야 한다.
    • stale time 이내에 같은 queryKey로 데이터를 요청하면, refectch 없이 캐시된 데이터를 얻을 수 있다.
  • data, isError, isFetched, isLoading 등 다양한 return 값을 사용할 수 있다.
    • isLoading/isError 값으로 로딩 중일 때/에러가 나타날 때 조건부 렌더링을 구현할 수 있다.

문제

  • 데이터를 수정/삭제한 후 데이터가 자동으로 재렌더링 되지 않는다.
  • 서버 데이터의 변경 작업을 요청할 때는 useMutation 훅을 사용해야 한다.
// components/TodoList.jsx

const fetchTodoList = async () => {
  const { data } = await axios.get(`${SERVER_URL}/todos`);
  return data;
};

export default function TodoList({ name }) {
  const isActiveList = name === 'active' ? true : false;
  const { isLoading, data: todos } = useQuery({
		// todos 라는 key로 데이터를 받아온다.
    queryKey: ['todos'],
		// 데이터를 요청하는 함수
    queryFn: fetchTodoList,
  });

  return (
    <styled.Container>
      {/* active/done 리스트에 따라 타이틀 표시 */}
      <styled.Title>{isActiveList ? 'Active 🔥' : 'Done ✅'}</styled.Title>
      {isLoading
        ? 'Loading...'
        : todos
            // active일 때는 isDone이 false인 값만 표시
            // done일 때는 inDone이 true인 값만 표시
            .filter((t) => isActiveList === !t.isDone)
            .map((t) => (
              <Todo todo={t.todo} isDone={t.isDone} key={t.id} id={t.id} />
            ))}
    </styled.Container>
  );
}

3. useMutation

Todo 수정/삭제 시 서버 요청을 다루는 쿼리이다.

  • mutationFn : 인자를 받아서 서버에 요청하는 함수
  • onSuccess : 서버 요청이 성공하면 실행되는 작업
    • queryClient의 invalidateQueries 메소드를 사용하면, queryClient가 가지고 있는 todos라는 queryKey를 무효화시킬 수 있다.
    • queryKey가 무효화되면 서버에서 데이터를 재호출한다. ⇒ 화면을 재렌더링 한다.
  • useQuery와 마찬가지로 data, isError, isSuccess, isIdle 등 다양한 return 값을 사용할 수 있다.
// AddForm.jsx

export default function AddForm() {
  const [todoValue, setTodoValue] = useState('');
  const [visible, setVisible] = useState(false);

  const mutationAdd = useMutation({
    mutationFn: async (newTodo) => {
      await axios.post(`${SERVER_URL}/todos`, newTodo);
    },
    onSuccess: () => {
      queryClient.invalidateQueries('todos');
    },
  });

  // input 값이 바뀔 때 todoValue 값을 업데이트
  const handleChange = (e) => {
    setTodoValue(e.target.value);
  };

  // form이 submit되면 실행되는 함수. todo를 추가함
  const handleSubmit = (event) => {
    event.preventDefault();

    const todo = todoValue.trim(); // todo 앞뒤 공백을 제거
    if (!todo) {
      // todo 입력값이 없으면 초기화 후 리턴
      setTodoValue('');
      setVisible(true); // 경고문구 표시
      return;
    }

    // DB에 todo 추가
    mutationAdd.mutate({ isDone: false, todo });
    setVisible(false); // 경고문구 숨김
    setTodoValue('');
  };

  return (
    <styled.FormContainer>
      <styled.Form onSubmit={handleSubmit}>
        <styled.Label htmlFor='new-todo'>To Do </styled.Label>
        <styled.Input
          type='text'
          id='new-todo'
          name='new-todo'
          onChange={handleChange}
          autoFocus={true}
          value={todoValue}
        />
        <styled.ErrorMessage visible={visible}>
          내용을 입력하세요.
        </styled.ErrorMessage>
        <Button value='추가' />
      </styled.Form>
    </styled.FormContainer>
  );
}



References

profile
Software Engineer 🍊
post-custom-banner

1개의 댓글

comment-user-thumbnail
2023년 1월 2일

오 리액트 쿼리까지 쓰셨네요 !

답글 달기