리액트/리액트

[리액트] 쿼리 훅?

삼록이 2025. 10. 28. 12:41

 

쿼리?

처음 리액트 쿼리라는 용어를 들었을 때는,

'내가 아는 쿼리는 SQL쿼리 밖에 없는데 리액트에도 쿼리라는 개념이 있나?' 하고 의아했다.

그러면 리액트에서 말하는 쿼리는 무엇인가?

DB의 쿼리용어랑 비슷한 개념이다.

데이터베이스에세 데이터를 받아오는 쿼리처럼

리액트에서 말하는 쿼리는 서버(API)에게 데이터를 받아오고 조작하는 것을 말한다.

SELECT * FROM users WHERE id = 1;
=>DB에 user 데이터를 달라고 요청하는 SQL 쿼리

axios.get('/api/users/1');
=>서버(API)에게 user 데이터를 달라고 요청하는 리액트 쿼리

 

즉, 서버에서 데이터를 가져오고 캐싱하는 행위다.(데이터를 요청(fetch) 하고 그 결과를 저장하고 관리하는 행위)

아래 한줄이 리액트에서 말하는 쿼리다.

단순히 axios로 데이터를 받아오는 게 아니라, React Query가 대신 요청을 관리하고 캐시해주는 구조다.

const { data, isLoading, error } = useQuery({
  queryKey: ['user', 1],
  queryFn: () => axios.get('/api/user/1'),
});

 

 

그렇다면 쿼리 훅이란?

쿼리 훅이란, ReactQuery에서 제공하는, 서버 데이터를 관리하는 훅함수.

대표적으로, 아래의 훅함수가 있다.

  • useQuery : 데이터를 가져오는 쿼리 훅
  • useMutation: 데이터를 변경하는 쿼리 훅

 


1.useQuery

:서버에서 데이터를 가져오는(GET) 요청을  담당하는 React Query의 핵심 훅.

 

React에서 데이터를 가져올 때, 아래처럼 컴포넌트가 처음 렌더링할 때 요청을 보낸다.

useEffect(() => {
  axios.get('/api/todos').then(res => setTodos(res.data))
}, [])

이렇게 하면 로딩상태, 에러상태, 캐시관리를 전부 직접 만들어야해서 복잡해진다.

이를 자동으로 쉽게 해주는게 useQuery다.

 

*기본 문법 구조

const { data, isLoading, error } = useQuery({
  queryKey: ['todos'],               // 쿼리 식별자 (캐시 키)
  queryFn: async () => {             // 데이터를 가져오는 함수
    const res = await axios.get('/api/todos')
    return res.data
  },
})

 

useQuery는 아래와 같은 반환값을 가지지만, 우리는 때에 따라 필요한 것만 구조분해할당으로 가져와서 사용한다.

(위에서 data, isLoading, error 만 가져온 것 처럼)

const {
  data,          // 성공 시 데이터
  error,         // 실패 시 에러 객체
  isLoading,     // 로딩 중 여부
  isError,       // 에러 여부 (true/false)
  isSuccess,     // 성공 여부 (true/false)
  refetch,       // 수동으로 다시 요청하는 함수
  status,        // 현재 상태 ("loading" | "error" | "success")
} = useQuery(...)

 

그리고 useQuery를 실행하면 실행흐름은 아래와 같다.

 

 

또한 enabled를 이용해 조건부 요청을 보낼 수도 있다.

const { data, isLoading } = useQuery({
  queryKey: ['user', userId],
  queryFn: () => axios.get(`/api/user/${userId}`),
  enabled: !!userId, // userId가 있을 때만 실행
});

 

React Query는 try~catch가 없어도 자동으로 에러를 잡아서 error에 담아준다.

const { data, error, isError } = useQuery({
  queryKey: ['todos'],
  queryFn: async () => {
    const res = await axios.get('/api/todos');
    if (res.status !== 200) throw new Error('데이터 요청 실패');
    return res.data;
  },
});

if (isError) console.error(error);

 

결과를 받아 가공도 가능하다.

const { data: userNames } = useQuery({
  queryKey: ['users'],
  queryFn: fetchUsers,
  select: (data) => data.map(u => u.name), // 데이터 후처리
});

 

2.useMutation

:서버의 데이터를 생성/수정/삭제하는 변경 요청을 관리하는 React Query 훅.

 

*기본 문법 구조

const mutation = useMutation({
  mutationFn: async (payload) => {
    const res = await axios.post("/api/resource", payload);
    return res.data;
  },
});

 

useQuery함수에서 실제 서버에 요청을 수행하는 함수였던 queryFn처럼 mutationFn이 서버에 데이터를 전송하여 생성,수정,삭제 요청을 하는 함수.

그리고 이 요청을 실행하는 방식은 mutate() 방식 과  mutateAsync() 방식이 있다.

 

1.  mutate() 방식

const mutation = useMutation({
  mutationFn: (newTodo) => axios.post('/api/todos', newTodo),
  onSuccess: () => alert("등록 성공!"),
  onError: () => alert("에러 발생!"),
});

mutation.mutate({ title: "React 공부" });
//mutationFn 실행됨
//성공시 OnSuccess 콜백 실행이되고 실패시 onError 콜백이 실행된다

 

2. mutateAsync() 방식

const { mutateAsync: addTodo } = useMutation({
  mutationFn: (newTodo) => axios.post('/api/todos', newTodo),
});

try {
  const result = await addTodo({ title: "React 공부" });
  console.log("성공:", result);
} catch (err) {
  console.error("에러:", err);
}

//await를 쓸 수 있어 비동기 로직 안에서 자연스럽게 에러 핸들링이 가능하다.

 

 

*반환값

*예시

const { mutateAsync: createSupport, isPending, isSuccess, error } = useMutation({
  mutationFn: async (payload) => {
    const res = await axiosInstance.post("/finder/program/support/new", payload);
    return res.data.data;
  },
});
mutateAsync → 이름을 createSupport로 변경

isPending → 로딩 상태 (true/false)

error → 에러 객체

data / isSuccess → 성공 상태

mutateAsync: createSupport 에서 mutateAsync: 옆의 createSupport는 내가 새로 붙인 이름(별칭 alias)다.

mutateAsync를 createSupport라는 이름으로 바꾸어 받겠다는 말이다.

(객체의 구조분해할당에서는 이런식으로 값을 꺼낼 때 이름을 바꿔서 받을 수 있다.)