'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 컴포넌트를 다른 컴포넌트를 감싸는 "래퍼" 컴포넌트로 사용한다.

GET 방식으로 서버의 요청을 받는 방법

#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 }에서 각각의 반환 값 의미.

POST 방식으로 서버에 요청을 보내는 방법

'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 차이점.