[React] json server CRUD with react-query

DaramGee·2025년 6월 11일

TIL

목록 보기
10/17

json-server CRUD

참고 : https://github.com/typicode/json-server
상태관리 구상 참고 : https://velog.io/@hyeon9782/%EC%9A%B0%EC%95%84%EC%BD%98-2023-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EC%83%81%ED%83%9C%EA%B4%80%EB%A6%AC-%EC%8B%A4%EC%A0%84-%ED%8E%B8-with-React-Query-Zustand

Routes

Based on the example db.json, you'll get the following routes:

GET    /posts
GET    /posts/:id
POST   /posts
PUT    /posts/:id
PATCH  /posts/:id
DELETE /posts/:id

# Same for comments
GET   /profile
PUT   /profile
PATCH /profile

Conditions

  • lt → <
  • lte → <=
  • gt → >
  • gte → >=
  • ne → !=
GET /posts?views_gt=9000

JSON Server + zustand, react-query 로 CRUD 처리하기

1. JSON Server 구축

  • db.json 파일에 loginMgr, chat 등 데이터베이스 구성
  • npm run server로 JSON Server 실행

2. 상태관리

  • 클라이언트(zustand)
  • 서버(react-query)

3. CRUD API 연동

  • getMgrDetail, getLoginMgr로 JSON Server에서 데이터 조회
  • saveLoginMgr, updateLoginStatus, updateChatMgr로 PATCH 요청 처리
  • zustand 상태(chatSeq)와 JSON Server 데이터가 동기화되도록 처리

4. UI 연동

  • RightPanelHeader, MyCounsel에서 zustand 상태와 API 호출 연결
  • 셀렉트박스에서 담당자 선택 시 saveLoginMgr 호출
  • 버튼 클릭 시 서버, 클라이언트 액션 처리

추후 처리할 부분: zustand → React Query 전환

🔄 전환이 필요한 이유

  • 클라이언트 상태 관리와 서버 상태(fetch/mutate)를 분리해 깔끔하게 관리
  • API 호출 캐싱, 리페치, 오류 처리 자동화
  • staleTime, refetch, 쿼리키 관리로 사용자 경험 개선

기존은 데이터 패칭을 위해서 여러 useState훅을 사용했었음
before :
const [isLoading, setLoading] = useState(false)
const [isError, setError] = useState(false)
const [data, setData] = useState({});
after :
const { status, data, error, isFetching } = useQuery(() => fetch(URL));
react query를 통해 한번에 제어가 가능해짐


🔄 React Query로 변경 예정인 부분

영역기존 방식(zustand)변경 예정(React Query)
로그인 정보 조회zustand + getLoginMgruseQuery로 loginInfo fetch
상담 데이터(fetch)zustand + getChatListuseQuery로 chatList fetch
상담 상세(fetch)zustand + getChatDetailuseQuery로 chatDetail fetch
로그인 상태 변경zustand + updateLoginStatususeMutation으로 updateLoginStatus
상담 상태 변경zustand + updateChatStatususeMutation으로 updateChatStatus
담당자 변경zustand + updateChatMgruseMutation으로 updateChatMgr

🔄 zustand로 유지할 부분

  • UI 토글 상태(예: 모달 열림/닫힘)
  • 현재 선택된 chatSeq
  • 현재 선택된 메뉴, sidebar 상태

✨ 추가 고려 사항

  • React Query로 상태 변경 시 zustand의 일부 상태(loginInfo)는 제거 가능
  • JSON Server 대신 실 API 연동으로 변경 시에도 React Query가 더 적합
  • DevTools와 연동해서 쿼리 상태 디버깅

1. queryKeys.ts 생성

  • 편의상 하나의 ts에 넣었으나 상황에 따라 각 ts로 만들어서 사용하는 것 추천
import { createQueryKeys } from '@lukemorales/query-key-factory';  
import type { Chat, User, Mgr } from '@/types';  
  
export const userKeys = createQueryKeys('user', {  
    all: { queryKey: ['user'] },  
    list: null,  
    detail: (userId:User['userId']) => [userId],  
});  
  
export const chatKeys = createQueryKeys('chat', {  
    all: { queryKey: ['chat'] },  
    list: (mgrId: Mgr['mgrId']) => [mgrId],  
    detail: (chatSeq: Chat['chatSeq']) => [chatSeq],  
});  
  
export const loginKeys = createQueryKeys('login', {  
    all: { queryKey: ['login'] },  
    byId: (id: number) => ({ queryKey: ['login', id] }),  
});

2.각 hook 생성

import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';  
import { loginKeys } from '@query/queryKeys';  
import { getLoginMgr, saveLoginMgr, updateLoginStatus } from '@api/loginApi';  
import type { Login, Mgr } from '@/types';  
  
export const useLogin = () => {  
    const loginInfoQuery = useQuery<Login>({  
        queryKey: loginKeys.all.queryKey,  
        queryFn: getLoginMgr,  
        /* 바로 비동기 호출을 넣을수도 있는 자리임.  
        queryFn: async () => {            const response = await Api.get<Login[]>('/loginMgr');            return response.data[0];        },        */    });  
  
    return {  
        loginInfo: loginInfoQuery.data,  
        isLoading: loginInfoQuery.isLoading,  
        error: loginInfoQuery.error,  
    };  
};  
  
export const useSaveLoginMgrMutation = () => {  
    const queryClient = useQueryClient();  
    return useMutation({  
        mutationFn: (mgrId: Mgr['mgrId']) => saveLoginMgr(mgrId),  
        onSuccess: () => {  
            //성공시, login관련 쿼리들 무효화 -> 재요청 발생(사용하는 컴포넌트 데이터 동기화 되는 부분)  
            queryClient.invalidateQueries({ queryKey: loginKeys.all.queryKey });  
        },  
    });  
};  
  
export const useUpdateLoginStatusMutation = () => {  
    const queryClient = useQueryClient();  
    return useMutation({  
        mutationFn: ({ loginInfo, status }: { loginInfo: Login; status: Login['status'] }) =>  
            updateLoginStatus(loginInfo, status),  
        onSuccess: () => {  
            queryClient.invalidateQueries({ queryKey: loginKeys.all.queryKey });  
        },  
    });  
};

3. 화면에서 사용

import { useLogin } from '@hooks/useLogin';  //hook import
import { useChat } from '@hooks/useChat';  
  
  
function MyCounsel() {  
    ...
    /*서버 영역 : react-query 관리*/  
    const { loginInfo, isLoading } = useLogin(); //서버에서 가져온 login정보 사용 가능
    ...

@lukemorales/query-key-factory는?

  • React Query에서 Query Key를 관리하는 도구
  • 쿼리 키 계층적 관리(중복 및 오타 방지)

lukemorales를 사용할 때 queryKeys.ts 예시

import { createQueryKeys } from '@lukemorales/query-key-factory';

export const chatKeys = createQueryKeys('chat', {
  list: (mgrId: number) => [mgrId],    // ['chat', mgrId]
  detail: (chatSeq: number) => [chatSeq],  // ['chat', chatSeq]
});

export const loginKeys = createQueryKeys('login', {
  all: null,   // ['login']
});

lukemorales를 사용하지 않을 때 queryKeys.ts 예시

export const chatKeys = {
  all: ['chat'] as const,
  list: (mgrId: number) => ['chat', 'list', mgrId] as const,
  detail: (chatSeq: number) => ['chat', 'detail', chatSeq] as const,
};

export const loginKeys = {
  all: ['login'] as const,
};

사용하지 않으면 배열을 직접 작성해야하고, 네임스페이스도 직접 관리 필요
사용하게 될 경우, 오타 방지 및 네임스페이스 자동화 가능하며 추후 유지보수도 쉬움


react query hook

useQuery 훅 : Read

useQuery 훅은 데이터를 불러오는 코드를 React Query 라이브러리에 등록하기 위해 사용

useQuery는 키와 데이터를 불러오는 비동기 함수를 인자로 받아 어플리케이션의 현재 상태를 나타내는 다양한 값을 반환

useMutation 훅 : Create, Update, Delete

useMutation 훅은 원격 데이터의 생성 / 업데이트 / 삭제에 사용

useMutation 훅은 데이터를 업데이트하기 위한 비동기 함수를 인자로 갖고 뮤테이션을 실행하기 위한 뮤테이트 함수를 반환


react-query 관련 용어

❓ 캐싱된다는 건?
react-query는 서버에서 받아온 데이터를 기억해두고,

같은 쿼리가 또 호출되면 서버에 다시 요청하지 않고 캐시에서 꺼내줌.

→ 즉, axios.get() 결과를 저장해두는 것

무효화(invalidate)란?
"이 캐시 오래됐을 수 있어. 다시 가져와!" 라는 명령.

react-query가 그 key에 해당하는 쿼리를 자동으로 다시 fetch
'로그인' 관련 데이터가 바뀌었으니 서버에 가서 다시 받아와!

queryClient.invalidateQueries({ queryKey: ['login'] });

0개의 댓글