To Do List 프로젝트 진행 중 서버에서 Todo 데이터를 받아오는 과정을 3단계에 걸쳐 개선했다.
useEffect
→ useQuery
→ useMutation
순으로 변경된 과정과 코드를 정리한 문서이다. 🙂
홈페이지인 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>
);
}
queryKey
로 데이터를 구분하고, 중복 요청을 처리할 수 있다.staleTime
이라는 옵션을 지정해야 한다.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>
);
}
Todo 수정
/삭제
시 서버 요청을 다루는 쿼리이다.
mutationFn
: 인자를 받아서 서버에 요청하는 함수onSuccess
: 서버 요청이 성공하면 실행되는 작업invalidateQueries
메소드를 사용하면, queryClient가 가지고 있는 todos라는 queryKey를 무효화시킬 수 있다.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
오 리액트 쿼리까지 쓰셨네요 !