코드 출처 : https://github.com/Cluster-Taek/next14-boilerplate
GitHub - Cluster-Taek/next14-boilerplate
Contribute to Cluster-Taek/next14-boilerplate development by creating an account on GitHub.
github.com
users/page.tsx
'use client';
const Page = () => {
const pageStyle = PageSva();
const { openModal } = useModals();
const handleClickCreateButton = () => {
openModal({
id: MODAL.USER_CREATE,
component: <UserCreateFormModal />,
});
};
return (
<Box className={pageStyle.wrapper}>
<Button onClick={handleClickCreateButton}>Create</Button>
</Box>
);
};
export default Page;
user-create-form-modal.tsx
import Button from '@/components/common/button';
import { MODAL } from '@/constants/modal-key-constants';
import useModals from '@/hooks/use-modals';
import { IUserCreateFormValue, useCreateUserMutation } from '@/lib/user';
import { sva } from '@/styled-system/css';
import { Controller, useForm } from 'react-hook-form';
const UserCreateFormModal = () => {
const userCreateFormStyle = UserCreateFormModalSva();
const { closeModal } = useModals();
const {
handleSubmit: formHandleSubmit,
formState,
control,
setError,
} = useForm<IUserCreateFormValue>({
defaultValues: {
name: '',
},
});
const { mutate: createUser } = useCreateUserMutation();
const handleSubmit = formHandleSubmit(async (data) => {
createUser(data, {
onSuccess: () => {
closeModal(MODAL.USER_CREATE);
},
onError: (error) => {
setError('name', {
type: 'manual',
message: error.message,
});
},
});
});
return (
<form onSubmit={handleSubmit} className={userCreateFormStyle.wrapper}>
<Controller
control={control}
name="name"
rules={{ required: 'Name is required' }}
render={({ field }) => (
<input type="text" {...field} className={userCreateFormStyle.input} placeholder="name" />
)}
/>
{formState.errors.name && <div className={userCreateFormStyle.error}>{formState.errors.name.message}</div>}
<Button type="submit">Submit</Button>
</form>
);
};
export default UserCreateFormModal;
const UserCreateFormModalSva = sva({
slots: ['wrapper', 'input', 'button', 'error'],
base: {
wrapper: {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
borderRadius: 'md',
shadow: 'md',
padding: '4',
width: 'sm',
margin: '0 auto',
backgroundColor: 'white',
},
input: {
width: 'full',
padding: '2',
marginTop: '2',
},
button: {
width: 'full',
padding: '2',
marginY: '2',
borderRadius: 'md',
backgroundColor: 'primary.01',
color: 'white',
'&:hover': {
backgroundColor: 'primary.02',
},
},
error: {
color: 'red',
marginTop: '2',
},
},
});
UserCreateFormModal
- 사용자 생성 폼 모달 컴포넌트입니다.
- 폼 제출과 유효성 검사 로직을 포함합니다.
- react-hook-form과 react-query를 사용하여 상태 관리 및 서버 요청을 처리합니다.
사용된 주요 라이브러리와 기능
- react-hook-form: 폼 상태를 관리하고 유효성 검사를 수행합니다.
- useForm 훅으로 폼 상태와 유효성 검사를 정의합니다.
- Controller 컴포넌트를 사용하여 react-hook-form의 제어 하에 커스텀 컴포넌트를 연결합니다.
- react-query: 데이터 요청 및 캐싱을 관리합니다.
- useCreateUserMutation 훅으로 사용자 생성 API 호출을 처리합니다.
- @styled-system/css 및 sva: 스타일링을 담당합니다.
- sva를 사용하여 스타일링을 컴포넌트화하고 구조적으로 관리합니다.
- useModals: 모달 관리 훅입니다.
- 모달을 닫는 동작(closeModal)을 수행합니다
코드 동작
- 폼 상태 초기화
- useForm 훅을 사용하여 기본 상태(defaultValues)를 { name: '' }으로 설정합니다.
- 폼 유효성 검사
- Controller 컴포넌트로 폼 필드를 제어하며, rules를 통해 이름 입력 필수 조건을 설정합니다.
- 이름이 비어있을 경우 'Name is required' 에러 메시지가 표시됩니다.
- 사용자 생성 요청 처리
- useCreateUserMutation의 mutate를 호출하여 서버에 데이터를 전송합니다.
- 요청 성공 시 모달을 닫습니다(closeModal).
- 요청 실패 시 setError로 폼 에러 메시지를 설정합니다.
- UI 스타일링
- UserCreateFormModalSva를 사용하여 스타일링을 설정합니다.
- 각 UI 요소(wrapper, input, button, error)의 스타일을 정의합니다.
- CSS-in-JS 방식을 통해 스타일과 컴포넌트를 통합적으로 관리합니다.
주요 컴포넌트 설명
<form>
- 폼 제출 이벤트(onSubmit)를 처리합니다.
- handleSubmit 함수는 폼 데이터와 API 호출 로직을 실행합니다.
<Controller>
- 이름 입력 필드를 관리합니다.
- 유효성 검사 실패 시 오류 메시지를 렌더링합니다.
Button
- type="submit" 속성을 사용해 폼 제출을 트리거합니다.
UserCreateFormModalSva
- sva 함수로 정의된 스타일 객체입니다.
- 각 UI 요소에 재사용 가능한 스타일을 적용합니다.
useForm
const {
handleSubmit: formHandleSubmit,
formState,
control,
setError,
} = useForm<IUserCreateFormValue>({
defaultValues: {
name: '',
},
});
- control: 폼 필드를 제어합니다.
- formState: 폼의 상태를 나타내며, 에러 정보를 포함합니다.
- setError: 폼 필드에 수동으로 에러를 설정합니다.
- handleSubmit: 폼 제출 핸들러를 감싸서 유효성 검사를 수행합니다.
const { mutate: createUser } = useCreateUserMutation();
const handleSubmit = formHandleSubmit(async (data) => {
createUser(data, {
onSuccess: () => {
closeModal(MODAL.USER_CREATE);
},
onError: (error) => {
setError('name', {
type: 'manual',
message: error.message,
});
},
});
});
- useCreateUserMutation
- React Query를 사용해 정의된 커스텀 훅입니다.
- 사용자 생성 API를 호출하는 로직을 캡슐화합니다.
- 반환된 객체에서 mutate 메서드를 createUser라는 이름으로 구조 분해 할당했습니다.
- mutate는 API 요청을 실행하는 함수입니다.
- formHandleSubmit
- react-hook-form에서 제공하는 함수입니다.
- 폼 데이터를 검증한 뒤, 제출 핸들러를 실행합니다.
- 이 코드에서는 handleSubmit이 폼 제출 시 호출됩니다.
- data
- 폼에서 제출된 데이터를 담고 있습니다.
- 여기서는 사용자의 입력값 { name: '' } 형태로 전달됩니다.
- createUser(data, { onSuccess, onError })
- createUser는 사용자 생성 API 호출을 실행합니다.
- 첫 번째 인수로 폼 데이터를 전달합니다.
- 두 번째 인수로 성공 및 실패 콜백을 포함한 옵션 객체를 전달합니다.
user.ts
import { fetchApi } from './base';
import { IPageable } from '@/types/pageable';
import { IUser } from '@/types/user';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
export interface IUsersParams {
// paging params of json-server
_page: number;
_per_page: number;
}
export interface IUserCreateFormValue extends Record<string, unknown> {
name: string;
}
export const useUsers = (params: IUsersParams) => {
return useQuery<IPageable<IUser>>({
queryKey: [`/api/users`, params],
});
};
export const useCreateUserMutation = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (data: IUserCreateFormValue) => await fetchApi.post(`/api/users`, data),
onSuccess: async () => {
await queryClient.invalidateQueries({
queryKey: ['/api/users'],
refetchType: 'all',
});
},
});
};
- 이 코드는 React Query를 활용하여 사용자 데이터를 조회하고, 새로운 사용자를 생성하는 두 가지 주요 기능을 제공하는 유틸리티 모듈입니다.
사용된 주요 라이브러리
- fetchApi: API 호출을 담당하는 커스텀 함수입니다.
- @tanstack/react-query: 데이터의 캐싱, 상태 관리, 서버와의 비동기 작업을 효율적으로 관리하기 위한 라이브러리입니다.
- useQuery: 데이터를 조회하고 캐싱하는 데 사용됩니다.
- useMutation: 데이터를 생성하거나 수정, 삭제하는 등의 작업에 사용됩니다.
- useQueryClient: React Query의 QueryClient 객체를 반환하며, 캐시를 직접 조작하거나 갱신하는 데 사용됩니다.
export interface IUsersParams {
_page: number; // 현재 페이지
_per_page: number; // 페이지 당 아이템 개수
}
export interface IUserCreateFormValue extends Record<string, unknown> {
name: string; // 사용자 이름
}
- IUsersParams: 사용자 목록을 요청할 때 사용하는 쿼리 매개변수 형식을 정의합니다.
- IUserCreateFormValue: 새 사용자를 생성할 때 전송하는 데이터 형식을 정의합니다.
- Record<string, unknown>를 확장하여 추가 필드를 허용합니다.
사용자 조회 (useUsers)
- useUsers 훅 호출 시, React Query가 /api/users와 params를 키로 캐시를 확인합니다.
- 캐시가 유효하지 않으면 서버에서 데이터를 가져옵니다.
- 데이터를 성공적으로 가져오면 IPageable<IUser> 형태로 반환합니다.
useUsers 훅
export const useUsers = (params: IUsersParams) => {
return useQuery<IPageable<IUser>>({
queryKey: [`/api/users`, params],
});
};
- 동작:
- 사용자 목록 데이터를 가져옵니다.
- React Query의 useQuery를 사용하여 서버 데이터를 캐싱하고, 쿼리 키에 따라 데이터를 관리합니다.
- 매개변수:
- params: 서버 요청 시 사용할 페이지 매개변수 (_page, _per_page).
- 결과:
- React Query의 useQuery에서 반환하는 값(데이터, 상태, 에러 등).
- 제공하는 기능:
- 캐싱: 동일한 쿼리 키로 다시 요청할 경우 서버로 데이터를 다시 가져오지 않고 캐시된 데이터를 반환.
- 자동 리페치: 쿼리가 유효하지 않을 때 자동으로 데이터를 다시 가져옵니다.
사용자 생성 (useCreateUserMutation)
- useCreateUserMutation의 mutate를 호출하여 새로운 사용자 데이터를 서버에 POST 요청합니다.
- 요청 성공 시 onSuccess가 실행되어 /api/users와 관련된 캐시 데이터를 무효화합니다.
- 무효화된 쿼리는 자동으로 서버에서 데이터를 다시 가져와 UI를 업데이트합니다.
useCreateUserMutation 훅
export const useCreateUserMutation = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (data: IUserCreateFormValue) => await fetchApi.post(`/api/users`, data),
onSuccess: async () => {
await queryClient.invalidateQueries({
queryKey: ['/api/users'],
refetchType: 'all',
});
},
});
};
queryClient
- useQueryClient 훅을 사용해 React Query의 QueryClient 인스턴스를 가져옵니다.
- 쿼리의 캐시를 수동으로 갱신하거나 무효화하는 데 사용됩니다.
mutationFn
- 사용자가 전송한 데이터를 기반으로 서버에 POST 요청을 보냅니다.
- fetchApi.post를 통해 /api/users 엔드포인트에 데이터를 전송합니다.
- IUserCreateFormValue 형식의 데이터를 받습니다.
onSuccess
- mutationFn이 성공적으로 완료되었을 때 실행됩니다.
- queryClient.invalidateQueries:
- /api/users 키와 관련된 모든 캐시 데이터를 무효화하고, 서버에서 데이터를 다시 가져옵니다.
- refetchType: 'all'은 모든 관련 쿼리를 다시 가져오도록 설정합니다.
'개발 공부 > React' 카테고리의 다른 글
| React-query useQuery, useMutation (0) | 2025.01.06 |
|---|---|
| Next.js 에서 모달 띄우기와 서버작업을 통한 유저 정보 받기 및 등록 (4) (0) | 2025.01.05 |
| Next.js 에서 모달 띄우기와 서버작업을 통한 유저 정보 받기 및 등록 (2) (0) | 2025.01.03 |
| Next.js 에서 모달 띄우기와 서버작업을 통한 유저 정보 받기 및 등록 (1) (1) | 2025.01.02 |
| Next.js에서 레이아웃 구성 (3) / ModalProvider (1) | 2024.12.29 |