개발자 박가나
[241127 TIL] 본캠프 41일차 (TanStack Query 실습) 본문
챌린지반에서 TanStack Query 실습을 진행하였다.
TanStack Query를 사용하는 이유를 이해하고 사용법에 익숙해지는 것이 목적이었기 때문에 초기에는 App.jsx 파일에 모든 코드가 모여있는 상태였고, 다음과 같은 순서에 따라 코드 리팩토링을 진행하였다.
- API 관련 코드를 별도의 파일로 분리
- Axios Instance 생성
- Axios Interceptor 생성
- useQuery 구현
- useMutation 구현
- invalidateQueries 사용
- useQuery를 Custom Hook으로 분리
- useMutation을 Custom Hook으로 분리
- 7~8단계에서 생성한 Custom Hook을 하나의 Hook으로 통합
- 낙관적 업데이트 적용
초기 코드
/* App.jsx */
function TodoList() {
const [todos, setTodos] = useState([]);
const [newTodo, setNewTodo] = useState("");
const fetchTodos = async () => {
const { data } = await axios.get("http://localhost:3000/todos");
setTodos(data);
};
const addTodo = async () => {
if (!newTodo.trim()) return;
const { data } = await axios.post("http://localhost:3000/todos", {
title: newTodo,
completed: false,
});
setTodos((prev) => [...prev, data]);
setNewTodo("");
};
const deleteTodo = async (id) => {
await axios.delete(`http://localhost:3000/todos/${id}`);
setTodos((prev) => prev.filter((todo) => todo.id !== id));
};
const toggleComplete = async (id, completed) => {
const { data } = await axios.patch(`http://localhost:3000/todos/${id}`, {
completed: !completed,
});
setTodos((prev) =>
prev.map((todo) =>
todo.id === id ? { ...todo, completed: data.completed } : todo,
),
);
};
useEffect(() => {
fetchTodos();
}, []);
}
export default TodoList;
변경 코드
main / App
/* main.jsx */
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import './index.css';
import App from './App.jsx';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const queryClient = new QueryClient();
createRoot(document.getElementById('root')).render(
<StrictMode>
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
</StrictMode>
);
/* App.jsx */
import { useState } from 'react';
import { useTodos } from './hooks/useTodos';
function TodoList() {
const [newTodo, setNewTodo] = useState('');
const { data: todos, isPending, isError, addMutation, deleteMutation, toggleMutation } = useTodos();
const addTodo = async () => {
if (!newTodo.trim()) return;
addMutation.mutate(newTodo);
setNewTodo('');
};
const deleteTodo = async (id) => {
deleteMutation.mutate(id);
};
const toggleComplete = async (id, completed) => {
toggleMutation.mutate({ id, completed: !completed });
};
if (isPending) return <div>로딩 중...</div>;
if (isError) return <div>데이터를 불러오는 과정에서 오류 발생</div>;
}
API
/* src/api/axiosInstance.js */
import axios from 'axios';
const todosAPI = axios.create({
baseURL: 'http://localhost:3000/todos',
timeout: 5000
});
todosAPI.interceptors.request.use((config) => {
console.log(config.baseURL);
return config;
});
todosAPI.interceptors.response.use(
(response) => {
console.log(response.data);
return response;
},
(error) => {
console.error(error);
return Promise.reject(error);
}
);
export default todosAPI;
/* src/api/todos.js */
import todosAPI from './axiosInstance';
export const fetchTodosAPI = async () => {
const response = await todosAPI.get('/');
return response.data;
};
export const addTodoAPI = async (title) => {
const response = await todosAPI.post('/', {
title,
completed: false
});
return response.data;
};
export const deleteTodoAPI = async (id) => {
await todosAPI.delete(`/${id}`);
};
export const toggleCompleteAPI = async ({ id, completed }) => {
const response = await todosAPI.patch(`/${id}`, {
completed
});
return response.data;
};
Custom Hook
/* src/hooks/useFetchTodos.js */
import { useQuery } from '@tanstack/react-query';
import { fetchTodosAPI } from '../api/todos';
export const useFetchTodos = () => {
return useQuery({
queryKey: ['todos'],
queryFn: fetchTodosAPI
});
};
/* src/hooks/useAddTodo.js */
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { addTodoAPI } from '../api/todos';
export const useAddTodo = () => {
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: addTodoAPI,
onMutate: async (newTodo) => {
await queryClient.cancelQueries({
queryKey: ['todos']
});
const prevTodos = queryClient.getQueryData(['todos']);
queryClient.setQueryData(['todos'], (prev) => [...prev, newTodo]);
return { prevTodos };
},
onError: (error, context) => {
console.log(error);
queryClient.setQueryData(['todos'], context.prevTodos);
},
onSettled: () => {
queryClient.invalidateQueries({
queryKey: ['todos']
});
}
});
return { addMutation: mutation };
};
/* src/hooks/useDeleteTodo.js */
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { deleteTodoAPI } from '../api/todos';
export const useDeleteTodo = () => {
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: deleteTodoAPI,
onMutate: async (id) => {
await queryClient.cancelQueries({
queryKey: ['todos']
});
const prevTodos = queryClient.getQueryData(['todos']);
queryClient.setQueryData(['todos'], (prev) => prev.filter((todo) => todo.id !== id));
return { prevTodos };
},
onError: (error, context) => {
console.log(error);
queryClient.setQueryData(['todos'], context.prevTodos);
},
onSettled: () => {
queryClient.invalidateQueries({
queryKey: ['todos']
});
}
});
return { deleteMutation: mutation };
};
/* src/hooks/useToggleTodo.js */
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { toggleCompleteAPI } from '../api/todos';
export const useToggleTodo = () => {
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: toggleCompleteAPI,
onMutate: async ({ id, completed }) => {
await queryClient.cancelQueries({
queryKey: ['todos']
});
const prevTodos = queryClient.getQueryData(['todos']);
queryClient.setQueryData(['todos'], (prev) => prev.map((todo) => (todo.id === id ? { ...todo, completed } : todo)));
return { prevTodos };
},
onError: (error, context) => {
console.log(error);
queryClient.setQueryData(['todos'], context.prevTodos);
},
onSettled: () => {
queryClient.invalidateQueries({
queryKey: ['todos']
});
}
});
return { toggleMutation: mutation };
};
/* src/hooks/useTodos.js */
import { useFetchTodos } from './useFetchTodos';
import { useAddTodo } from './useAddTodo';
import { useDeleteTodo } from './useDeleteTodo';
import { useToggleTodo } from './useToggleTodo';
export const useTodos = () => {
const { data, isPending, isError } = useFetchTodos();
const { addMutation } = useAddTodo();
const { deleteMutation } = useDeleteTodo();
const { toggleMutation } = useToggleTodo();
return { data, isPending, isError, addMutation, deleteMutation, toggleMutation };
};
'내일배움캠프' 카테고리의 다른 글
[241129 TIL] 본캠프 43일차 (useInfiniteQuery) (1) | 2024.11.29 |
---|---|
[241128 TIL] 본캠프 42일차 (HTTP와 HTTPS) (0) | 2024.11.28 |
[241126 TIL] 본캠프 40일차 ('MBTI' 프로젝트 기능 구현) (1) | 2024.11.26 |
[241125 TIL] 본캠프 39일차 ('MBTI' 프로젝트 기능 구현) (1) | 2024.11.25 |
[241122 TIL] 본캠프 38일차 (1) | 2024.11.22 |