'use client';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactNode, useState } from 'react';
export default function Providers({ children }: { children: ReactNode }) {
const [client] = useState(() => new QueryClient());
return <QueryClientProvider client={client}>{children}</QueryClientProvider>;
}
내부에서 QueryClientProvider
를 사용해 React Query의 컨텍스트를 하위 컴포넌트에 전달한다.
import Providers from "./providers";
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body>
<Providers>
{children}
</Providers>
</body>
</html>
);
}
{ children }
을 넣는 이유는 React의 컴포넌트 컴포지션(composition) 개념 때문이다.
Providers
컴포넌트를 다른 컴포넌트를 감싸는 "래퍼" 컴포넌트로 사용한다.
#Next.js의 클라이언트 컴포넌트로 사용하겠다는 선언
'use client';
import { useEffect, useRef } from "react";
import { useQuery } from '@tanstack/react-query';
#Fecth API를 이용해 게시글 목록을 가져오는 함수
async function fetchPosts() {
const res = await fetch('<https://jsonplaceholder.typicode.com/posts>');
if (!res.ok) throw new Error('네트워크 응답 에러');
return res.json();
}
export default function Home() {
const { data, isLoading, isError, error } = useQuery({
queryKey: ['posts'],
queryFn: fetchPosts,
});
#데이터를 가져올 때 출력되는 내용
if (isLoading) return <p>로딩중</p>;
#에러가 발생했을 경우 에러 메시지를 출력.
if (isError) return <p>에러 발생:{(error as Error).message}</p>;
return (
<div>
<h1>게시글 목록</h1>
<ul>
{data.map((post: any) => (
<li key={post.id}>
<strong>{post.title}</strong>
<p>{post.body}</p>
</li>
))}
</ul>
</div>
);
}
const { data, isLoading, isError, error }에서 각각의 반환 값 의미.
data
: 가져온 게시글 배열isLoading
: 로딩 중 여부isError
: 오류 발생 여부error
: 실제 오류 객체'use client'
import { useState } from 'react';
import { useMutation } from '@tanstack/react-query';
//게시글 생성 시 보낼 데이터 타입스크립트
interface NewPost {
title: string;
body: string;
userId: number;
}
//서버 응답 타입
interface CreatedPost extends NewPost {
id: number;
}
async function createPost(newPost: NewPost): Promise<CreatedPost>//Promise에서 CreatedPost를 반환함.
{
const res = await fetch('<https://jsonplaceholder.typicode.com/posts>', {
method: 'POST', //POST방식으로 요청을 보냄.
headers: {
'Content-Type': 'application/json', //Content-Type 설정.
},
body: JSON.stringify(newPost) //JSON형태의 문자열로 변경.
});
if (!res.ok) throw new Error('게시글 생성 실패'); //요청 실패시 에러 출력
return res.json(); //전송하고 나서 변경된 값을 반환한다.
}
export default function PostCreator() {
const [title, setTitle] = useState('');
const [body, setBody] = useState('');
const { mutate, data, isPending, isSuccess, isError, error } = useMutation<CreatedPost, Error, NewPost>({
mutationFn: createPost //creatPost 함수를 실행.
});
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
mutate({
title,
body,
userId: 1 //로그인한 사용자의 ID라고 가정
});
};
return (
<div>
<h2>게시글 작성</h2>
<form onSubmit={handleSubmit}>
<input type="text" placeholder="제목" value={title} onChange={(e) => setTitle(e.target.value)}/>
<br />
<textarea placeholder="내용" value={body} onChange={(e) => setBody(e.target.value)}/>
<br />
<button type="submit" disabled={isPending}>
{isPending ? '작성 중...' : '작성하기'}
</button>
</form>
{isError && <p style={{ color: 'red' }}>에러: {error.message}</p>}
{isSuccess && data && ( //isSuccess로 성공하고 반환된 data를 출력한다.
<div>
<h3>게시 성공!</h3>
<p>ID: {data.id}</p>
<p>제목: {data.title}</p>
<p>내용: {data.body}</p>
</div>
)}
</div>
);
}
속성 이름 | 훅 종류 | 설명 |
---|---|---|
isPending |
useMutation() |
뮤테이션 요청 중 |
isLoading |
useQuery() |
데이터 패칭 중 |
isSuccess |
둘 다 | 요청이 성공했을 때 |
isError |
둘 다 | 요청 중 에러가 발생했을 때 |
타입스크립트의 Interface 와 Type 차이점.