웹 프로그래밍/React
[React] react-query 사용하기
범범조조
2023. 3. 30. 20:17
참고
https://kyounghwan01.github.io/blog/React/react-query/basic/#%E1%84%89%E1%85%A1%E1%84%8B%E1%85%AD%E1%86%BC%E1%84%92%E1%85%A1%E1%84%82%E1%85%B3%E1%86%AB-%E1%84%8B%E1%85%B5%E1%84%8B%E1%85%B2
개요
- Vite 을 통해 테스트 진행할 리액트 프로젝트 하나를 생성합니다.
- 생성된 프로젝트에 react-query 를 다운받고, CRUD 기능을 통해 react-query 사용 방법에 대해서 익힙니다.
사용 기술
react-query 테스트 프로젝트를 개발하기 위해, 사전에 필요한 라이브러리 정보는 다음과 같습니다.
- vite
- react-query
- react-query-devtools
- json-server
- uuid
테스트 프로젝트에서 사용될 라이브러리 목록은 위와 같습니다.
각 라이브러리는 프로젝트 진행해 나가면서, 필요할 때 마다 설치 진행하겠습니다.
Vite 설치
- 저는 Vite 를 사용해서 프로젝트를 진행해 나가려고 합니다.
- Vite 는 빠르고 간결한 모던 웹 프로젝트 개발 경험에 맞춰 탄생항 빌드 도구입니다.
- 자세한 내용은 https://vitejs-kr.github.io/guide/ 해당 사이트에서 확인하시면 됩니다.
- 앞서 생성한 리액트 프로젝트에서 Vite 를 설치 해줍니다.
- Vite 설치하는 방법은 아래와 같습니다.
npm create vite@latest ./
위 명령어를 통해 Vite를 설치 합니다.
정상적으로 설치가 되었다면, 아래와 같이 프로젝트가 생성된 것을 확인할 수 있습니다.
- 다음으로,
npm install
명령을 통해 package.json 파일의 의존이 되어있는 패키지들을 설치 진행해줍니다.
npm install
- 여기까지 모두 완료 하였다면, 이제 정상적으로 실행 되는지 확인을 위해서 Vite 를 실행합니다.
- Vite 를 실행하는 명령어는 다음과 같습니다.
npm run dev
npm run dev
명령을 실행하게 되면, 아래와 같이 로그가 출력됩니다.
VITE v4.2.1 ready in 1015 ms
➜ Local: http://localhost:5173/
➜ Network: use --host to expose
➜ press h to show help
- 위에서
http://localhost:5173
주소가 보이는데, 여기로 접속 합니다. - 문제 없이 설치가 되었다면, 아래와 같이
Vite + React
화면이 나오는 것을 확인할 수 있습니다.
불필요한 파일 삭제
- 다음으로, 프로젝트 개발에 앞서 필요없는 파일 3가지를 우선 삭제 하도록 하겠습니다.
- 삭제 진행할 파일 목록은 다음과 같습니다.
- assets
- App.css
- index.css
- 앞서 Vite 를 통해 생성했던 프로젝트에서 위 3가지 파일을 삭제합니다.
- 위와 같이 삭제를 완료 하였다면, 남은 파일 목록은 아래와 같습니다.
Router 구성
- 앞에서 불필요한 파일을 삭제 하였다면, 이제 라우터를 구성합니다.
- 먼저, 라우터를 구성하기 위해 필요한 패키지인
react-router-dom
패키지를 설치 합니다. - 설치 명령어는 아래와 같습니다.
npm i react-router-dom
- 설치가 완료 되었다면, main.jsx 에
BrowserRouter
를 추가해 줍니다.
import React from "react";
import ReactDOM from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import App from "./App";
ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
);
- 위와 같이
BrowserRouter
를 추가해 주었습니다.
App.jsx 에 페이지 라우터 구성
- React-Query 테스트를 위해, 총 3개의 페이지를 구성합니다.
- 아직 페이지를 생성하지는 않았지만, 아래와 같이 PostLists, Post, EditPost 3개의 페이지 Router 를 구성해 줍니다.
import { useState } from "react";
function App() {
return (
<div className="App">
<h1>Awesome blog</h1>
<Routes>
<Route path="/" element={<PostLists />} />
<Route path="/post/:id" element={<Post />} />
<Route path="/post/:id/edit" element={<EditPost />} />
</Routes>
</div>
);
}
export default App;
- 위에서 PostLists, Post, EditPost 페이지는 하나씩 구현해 나갈 예정입니다.
react-query 설치
- App.jsx 에 Router 구성이 완료 되었다면, 페이지와 컴포넌트를 추가하기에 앞서 react-query 를 먼저 설치를 진행합니다.
- react query 의 공식 사이트는 https://tanstack.com/query/v3/ 해당 링크를 통해 확인하실 수 있습니다.
- react-query 설치하는 명령어는 아래와 같습니다.
npm i @tanstack/react-query
- react-qeury 를 추가 완료 하였다면, react-query 전용 devtools 도 설치해줍니디다.
npm i @tanstack/react-query-devtools
- 위와 같이 설치가 모두 완료 되었다면, react-query 를 사용할 준비는 모두 마쳤습니다.
- react-query, react-query devtools 를 사용하려면, main.jsx 에 관련 속성을 추가해 주어야 합니다.
import React from "react";
import ReactDOM from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import App from "./App";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
// create a client
const queryClient = new QueryClient();
ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<BrowserRouter>
<QueryClientProvider client={queryClient}>
<App />
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
</BrowserRouter>
</React.StrictMode>
);
json-server 설치 및 구성
- react-query 를 사용하기에 앞서, client 에서 서버쪽으로 데이터를 요청해서 조회해야 할 서버 쪽 데이터가 필요합니다.
- 하지만, 서버를 구성하기에는 워낙 시간적 비용이 많이 들기 떄문에 원활한 테스트를 위해 json-server 를 이용하여 가짜 서버 역학을 하려고 합니다.
- json-server 설치는 다음 명령어를 통해 가능합니다.
npm install -g json-server
- 설치가 완료 되었으면, 프로젝트 root 경로에 db.json 파일을 하나 생성해 줍니다.
- 그리고 아래와 같이 테스트 데이터를 json 형식으로 추가합니다.
{
"posts": [
{
"id": "1",
"title": "title",
"body": "body"
}
]
}
- 우선 posts 이름을 가진 데이터 Array 가 있고, 여기에는 1개의 item 을 가지고 있는 형태의 json 데이터를 하나 생성하였습니다.
- 그리고 json-server 를 실행시켜 주면 됩니다.
- json-server 실행 시켜주는 명령어는 아래와 같습니다.
json-server --watch db.json
- 위 명령어를 통해, json-server 를 실행시킬 수 있습니다.
- 정상적으로 실행 되었다면, 아래와 같이 로그가 출력됩니다.
\{^_^}/ hi!
Loading db.json
Done
Resources
http://localhost:3000/posts
Home
http://localhost:3000
Type s + enter at any time to create a snapshot of the database
Watching...
CRUD API 서비스 추가
- 다음으로 react-query 를 이용하여 서버의 데이터를 CRUD 하기 위한 api 파일 하나를 생성합니다.
src/api/posts.jsx
경로에 posts.jsx 파일을 하나 생성 후, 서버의 데이터를 요청하는 로직을 추가해 줍니다.- 여기서 저는 fetch API 를 사용하였습니다.
export async function fetchPosts() {
const response = await fetch("http://localhost:3000/posts");
return response.json();
}
export async function fetchPost(id) {
const response = await fetch(`http://localhost:3000/posts/${id}`);
return response.json();
}
export async function createPost(newPost) {
const response = await fetch(`http://localhost:3000/posts`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(newPost),
});
return response.json();
}
export async function updatePost(updatedPost) {
const response = await fetch(`http://localhost:3000/posts/${updatedPost.id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(updatedPost),
});
return response.json();
}
export async function deletePost(id) {
const response = await fetch(`http://localhost:3000/posts/${id}`, {
method: "DELETE",
});
return response.json();
}
- 위에서 작성한 함수들을 바로 뒤에서 만들 페이지 및 컴포넌트에서 사용할 예정입니다.
페이지 및 컴포넌트 추가
- 앞서, react-query 와 react-query devtools 설정을 완료 하였습니다.
- 또한, App.jsx 에 3개의 페이지 전환을 위한 라우터 구성도 완료하였습니다.
- 이제 앞서 추가했던, PostLists, Post, EditPost 3개의 페이지 및 관련 컴포넌트들을 추가하도록 하겠습니다.
Post 페이지 생성
src/pages/Post.jsx
경로로 해서 Post 페이지를 추가하였습니다.- Post.jsx 코드는 다음과 같습니다.
import { useQuery } from "@tanstack/react-query";
import { useParams } from "react-router-dom";
import { fetchPost } from "../api/posts";
import { useNavigate } from "react-router-dom";
export default function Post() {
const navigate = useNavigate();
const { id } = useParams();
const {
isLoading,
isError,
data: post,
error,
} = useQuery({
queryKey: ["posts", id],
queryFn: () => fetchPost(id),
});
if (isLoading) return "loading...";
if (isError) return `Error: ${error.message}`;
return (
<div>
<button
onClick={() => {
navigate("/");
}}
>
back to list posts
</button>
<h1>{post.title}</h1>
<p>{post.author}</p>
</div>
);
}
- Post.jsx 페이지 코드를 보시면, useQuery 를 이용하여 데이터를 불러오는 것을 확인하실 수 있습니다.
- useQuery 는 데이터를 get 하기 위한 api 입니다.
- post, update, delete 는 useMutation 을 사용합니다.
- 첫 번째 파라미터로 unique key 가 들어가고, 두 번째 파라미터로 비동기 함수(api 호출 함수) 가 들어갑니다. (두 번째 파라미터는 promise 가 들어가야 합니다.)
- 첫 번째 파라미터로 설정한 unique Key 는 다른 컴포넌트에서도 해당 키를 사용하면 호출 가능합니다. unique key 는 string 배열을 받습니다. 배열로 넘기면 0번 값은 string 값으로 다른 컴포넌트에서 부를 값이 들어가고 두 번째 값을 넣으면 query 함수 내부에 파리미터로 해당 값이 전달됩니다.
- return 값은 api의 성공, 실패여부, api return 값을 포함한 객체입니다.
- useQuery는 비동기로 작동합니다. 즉, 한 컴포넌트에 여러개의 useQuery가 있다면 하나가 끝나고 다음 useQuery가 실행되는 것이 아닌 두개의 useQuery가 동시에 실행됩니다. 여러개의 비동기 query가 있다면 useQuery보다는 밑에 설명해 드릴 useQueries를 권유드립니다.
- enabled를 사용하면 useQuery를 동기적으로 사용 가능합니다.
PostList 페이지 생성
src/pages/PostList.jsx
경로로 해서 PostList 페이지를 추가하였습니다.- PostList.jsx 코드는 다음과 같습니다.
import AddPost from "../components/AddPost";
import { QueryClient, useQueryClient, useMutation, useQuery } from "@tanstack/react-query";
import { deletePost, fetchPosts } from "../api/posts";
import { useNavigate } from "react-router-dom";
import { useEffect, useMemo, useState } from "react";
export default function PostLists() {
const queryClient = useQueryClient();
const navigate = useNavigate();
const {
isLoading,
isError,
data: posts,
error,
} = useQuery({
queryKey: ["posts"],
queryFn: fetchPosts,
});
const deletePostMutation = useMutation({
mutationFn: deletePost,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["posts"] });
},
});
const handleDelete = (id) => {
deletePostMutation.mutate(id);
};
if (isLoading) return "loading...";
if (isError) return `Error: ${error.message}`;
return (
<div>
<AddPost />
{posts.map((post) => (
<div key={post.id} style={{ background: "#777" }}>
<h4 style={{ cursor: "pointer" }} onClick={() => navigate(`/post/${post.id}`)}>
{post.title}
</h4>
<button onClick={() => navigate(`/post/${post.id}/edit`)}>Edit</button>
<button onClick={() => handleDelete(post.id)}>Delete</button>
</div>
))}
</div>
);
}
EditPost 페이지 생성
src/pages/EditPost.jsx
경로로 해서 EditPost 페이지를 추가하였습니다.- EditPost.jsx 코드는 다음과 같습니다.
import PostForm from "../components/PostForm";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { useParams } from "react-router-dom";
import { fetchPost, updatePost } from "../api/posts";
import { useNavigate } from "react-router-dom";
export default function EditPost() {
const queryClient = useQueryClient();
const navigate = useNavigate();
const { id } = useParams();
const {
isLoading,
isError,
data: post,
error,
} = useQuery({
queryKey: ["posts", id],
queryFn: () => fetchPost(id),
});
const updatePostMutation = useMutation({
mutationFn: updatePost,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["posts"] });
navigate("/");
},
});
if (isLoading) return "loading...";
if (isError) return `Error: ${error.message}`;
const handleSubmit = (updatePost) => {
updatePostMutation.mutate({ id, ...updatePost });
};
return (
<div>
<PostForm onSubmit={handleSubmit} initialValue={post} />
</div>
);
}
AddPost.jsx 컴포넌트 생성
- 앞서 페이지에서 사용하는 공통 컴포넌트 중 하나인 AddPost.jsx 컴포넌트 코드입니다.
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { createPost } from "../api/posts";
import PostForm from "./PostForm";
import { v4 as uuidv4 } from "uuid";
export default function AddPost() {
const queryClient = useQueryClient();
const createPostMutation = useMutation({
mutationFn: createPost,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["posts"] });
console.log("success bro!");
},
});
const handleAddPost = (post) => {
createPostMutation.mutate({
id: uuidv4(),
...post,
});
};
return (
<div>
<h2>Add new post</h2>
<PostForm onSubmit={handleAddPost} initialValue={{}} />
</div>
);
}
- 위 코드에서 핵심은 useMutation react-query 에서 제공해주는 hook 을 이용하여 데이터를 추가해주고 있는 부분입니다.
- useMutation 을 값을 바꿀때 사용하는 api 입니다.
- 앞서,
src/api/posts.jsx
API 에서 만든 createPost 함수를mutationFn
에 넣어 주었습니다.
PostForm.jsx 컴포넌트 생성
- 앞서 페이지에서 사용하는 공통 컴포넌트 중 하나인 PostForm.jsx 컴포넌트 코드입니다.
import { useState } from "react";
export default function PostForm({ onSubmit, initialValue }) {
const [post, setPost] = useState({
title: initialValue.title || "",
body: initialValue.body || "",
});
const handleChangeInput = (e) => {
setPost({
...post,
[e.target.name]: e.target.value,
});
};
const handleSubmit = (e) => {
e.preventDefault();
onSubmit(post);
setPost({
title: "",
body: "",
});
};
const renderField = (label) => (
<div>
<label>{label}</label>
<input onChange={handleChangeInput} type="text" name={label.toLowerCase()} value={post[label.toLowerCase()]} />
</div>
);
return (
<form onSubmit={handleSubmit}>
{renderField("Title")}
{renderField("Body")}
<button type="submit">Submit</button>
</form>
);
}
전체 프로젝트 구조
- 이제 코드는 모두 작성 완료 하였습니다.
- 완성된 프로젝트의 폴더 구조는 아래와 같습니다.
실행 결과
728x90