기존에는 react-router-dom
의 BrowserRouter
컴포넌트를 이용하여 작업하였다. 그런데 createBrowserRouter
라는 함수를 알게되었다. createBrowserRoute
는 react-router-dom
공식 문서에서 new라는 뱃지를 붙여놓고 가장 추천하고 있는 최신 방식이다.
createBroswerRouter
를 사용하면 페이지의 연결 구조를 한눈에 살펴보기 쉽고 여러 가지 옵션을 제공해준다. 다음에 살펴볼 비동기처리에서도 나오겠지만 요즘은 에러, 데이터 fetching 등을 UI와 분리해서 작업하게 해주는 도구들이 많이 나오는 것 같다.
loader와 action을 사용해서 데이터 fetching과 mutation을 UI 컴포넌트와 분리시켜줄 수 있는 것 같다. 나는 loader에 토큰 검사하는 로직을 넣어서 로그인이 필요한 컴포넌트를 보호해줄 수 있게 했다. 또 logout 같이 UI 컴포넌트가 따로 필요없는 경로에도 loader를 넣어주었다.
그리고 TodoList의 children으로 Detail 페이지를 넣어주어서 todoList의 loader는 공유하되 컴포넌트를 분리할 수 있게 만들었다. (원래는 list 페이지 안에 상세 페이지도 함께 있었다.)
최종 결과물은 아래와 같다.Create
Edit
Delete
Detail
같은 CRUD 컴포넌트들을 라우터로 분리해주었다.
const router = createBrowserRouter([
{
path: MAIN_URL,
element: <Root />,
errorElement: <ErrorPage />,
children: [
{ index: true, loader: mainLoader },
{
path: LOGOUT_URL,
loader: logoutLoader,
},
{
path: LOGIN_URL,
element: <Login />,
},
{
path: SIGN_UP_URL,
element: <SignUp />,
},
{
path: TODO_LIST_URL,
element: <TodoIndex />,
loader: todoListLoader,
children: [
{ path: TODO_CREATE_URL, element: <Create /> },
{
path: TODO_DETAIL_URL,
element: (
<React.Suspense
fallback={
<Skeleton variant="rectangular" width={300} height={220} />
}
>
<Detail />
</React.Suspense>
),
children: [
{
path: TODO_DELETE_DETAIL_URL,
element: <Delete />,
},
{
path: TODO_EDIT_DETAIL_URL,
element: <Edit />,
},
],
},
],
},
],
},
]);
useMutation을 제대로 사용하고 있지 않다는 생각이 들어서 먼저 react query 공식 블로그인 tkdodo의 블로그를 보았다.
useMutation
의 onSuccess 콜백과 mutate
의 onSuccess 콜백의 차이를 몰랐었는데 블로그를 보고 알수 있었다.
useMutation의 callback
mutate의 callback
이전에는 mutation 후에 리스트를 다시 받아오기 위해 refetch를 받아와서 작업하고 있었는데 공식 문서에서 추천한 세가지 방법이 있었다.
const queryClient = useQueryClient();
onSuccess: () => {
queryClient.invalidateQueries(["todos"]);
},
2.Direct Updates
POST나 PUT 같은 API 요청을 했을 때 response로 받아오는 값에 data가 있다면 refetch를 하지 않고 바로 업데이트 해줄 수도 있다.
const useUpdateTitle = (id) => {
const queryClient = useQueryClient()
return useMutation(
(newTitle) => axios
.patch(`/posts/${id}`, { title: newTitle })
.then(response => response.data),
{
// 💡 response of the mutation is passed to onSuccess
onSuccess: (newPost) => {
// ✅ update detail view directly
queryClient.setQueryData(['posts', id], newPost)
},
}
)
}
일단 두번째 방법은 요청 후 response 값에서 따로 데이터를 받아오고 있지 않기 때문에 제외했다. 그리고 세번째 방법보다는 첫번째 방법이 더 안전하게 데이터를 업데이트해주고 사용자에게 혼란을 주지 않을 수 있다 생각해서 invalidateQueries
를 사용하기로 결정했다.