1. useForm
react-hook-form 에서 제공하며, 폼 상태 관리 및 입력값 검증을 간편하게 처리할 수 있다.
(폼 데이터 관리 / 유효성 검사 / 폼 제출 처리 / 오류 상태 관리 등)
useForm 훅의 리턴값
- register: 각 입력 필드를 react-hook-form 에 등록 (폼의 데이터를 추적/관리)
- handleSubmit: 폼 제출 이벤트를 처리하는 함수로 유효성 검사를 통과한 데이터만 전달
- formState: 폼 관련 상태 정보 제공
1.1. register
email 과 password 폼을 register 에 등록하는 코드
스프레드 연산자로 register 를 펼쳐주고 각각 'email', 'password' 라는 이름으로 register 에 등록해준다.
required 는 유효성 검사 옵션인데, required 옵션을 사용하면 필수조건이 된다. (required 를 사용했는데, 폼에 입력하지 않으면 오류남)
(이외에도 유효성 검사 옵션으로는 길이제한 / pattern / validate 등이 있으니 필요하면 찾아보자)
const {
register,
handleSubmit,
formState: { errors },
} = useForm<SignupProps>();
// ...
<input
type="email"
{...register('email', { required: true })}
/>
<input
type="password"
{...register('password', { required: true })}
/>
required 에는 위의 코드처럼 boolean 값을 넣어줘도 되고 문자열을 넣어줘도 되는데,
문자열을 넣어준다면 errors 객체의 message 부분에 들어가게 된다. 그래서 오류 메세지를 출력할 때 errors.email.message 로 접근해서 사용할 수 있다.
errors = {
email: {
type: "required",
message: "Email is required",
},
};
{errors.email && <p>{errors.email.message}</p>}
1.2. handleSubmit
handleSubmit 은 내부적으로 두 가지 작업을 처리한다.
- 유효성 검사 실행 -> 폼에 등록된 필드에 설정된 유효성 검사 규칙 평가 실행, 실패 시 formState.errors 객체에 오류를 저장 (제출X)
- onSubmit 콜백 호출 -> 유효성 검사 성공 시 폼 데이터를 인자로 받아 사용자가 정의한 콜백함수 onSubmit 을 호출
handleSubmit 함수는 유효성 검사만 도와줄 뿐 제출된 데이터를 어떻게 처리할지는 알지 못하기 때문에 onSubmit 함수는 사용자가 필요한 데이터 처리 로직에 따라 정의해야 한다.
const {
register,
handleSubmit,
formState: { errors },
} = useForm<SignupProps>();
const onSubmit = (data: SignupProps) => { // 사용자 정의 함수
signup(data).then((res) => {
// 성공
showAlert('회원가입이 완료되었습니다');
navigate('/login');
})
};
1.3. formState
formState 는 errors, isDirty, isValid 의 정보를 제공한다.
- errors: 입력 필드의 오류 상태를 담고 있는 객체
- isDirty: 사용자가 값을 변경했는지 여부
- isValid: 폼의 유효성 검사를 통과했는지 여부
그 중 errors 는 오류에 대한 정보를 담고 있는데, 이렇게 생겼다.
errors = {
email: {
type: "required",
message: "Email is required",
},
password: {
type: "minLength",
message: "Password must be at least 6 characters",
},
};
2. HTTP Client 생성하기
HTTP 클라이언트는 서버에 HTTP 요청을 보내고, 서버로부터 받은 응답을 처리하는 도구이다.
axios 는 HTTP 요청을 보낼 수 있는 라이브러리이고, AxiosRequestConfig 는 axios 에서 요청 설정을 정의하는 인터페이스로 baseURL, headers, timeout 같은 옵션을 포함한다.
옵션 구성
- baseURL: 기본 URL 설정
- timeout: 요청 제한 시간으로 이 시간을 넘기면 요청 중단
- headers: 요청의 타입
- withCredentials: 쿠키, 인증 토큰 등 증명을 포함
- ...config: 호출 시 추가로 전달되는 설정을 병합
import axios, { AxiosRequestConfig } from "axios";
const BASE_URL = 'http:~~';
const DEFAULT_TIMEOUT = 30000; // 3초
export const createClient = (config?: AxiosRequestConfig) => {
const axiosInstance = axios.create({
baseURL: BASE_URL,
timeout: DEFAULT_TIMEOUT,
headers: {
'content-type': 'application/json'
},
withCredentials: true,
...config,
});
axiosInstance.interceptors.response.use((response) => {
return response;
},
(error) => {
return Promise.reject(error);
}
);
return axiosInstance;
};
export const httpClient = createClient();
생성된 http 클라이언트는 필요한 곳에 불러서 사용할 수 있다.
baseURL/category 라는 url 로 get 요청을 보내면 서버에서 요청을 받아서 필요한 정보를 보내준다.
서버에서 요청에 대한 응답으로 받은 response 객체에는 status, headers, data 등 여러 정보들이 포함되어 있는데, 우리가 요청한 데이터는 response 의 data 필드에 들어가 있기 때문에 response.data 를 반환해준다.
import { Category } from "../models/category.models";
import { httpClient } from "./http";
export const fetchCategory = async() => {
const response = await httpClient.get<Category[]>('/category');
return response.data;
};
※ 추가 - CORS 에러 해결
http 클라이언트 생성하고 설정한 뒤에 서버에 요청을 보내려하면 계속해서 runtime 에러가 났는데, 서버 코드에 app.js 파일에 CORS 설정을 해주니 에러없이 잘 돌아갔다. (아래의 코드를 붙여주었음!)
CORS(Cross-Origin-Resource Sharing) 오류는 는 프론트엔드와 백엔드가 서로 다른 도메인, 프로토콜, 포트에서 실행될 때 발생하는데, 브라우저는 보안상의 이유로 서로 다른 출처 간에 데이터를 공유하는 요청을 차단한다. 때문에 프론트엔드에서 백엔드로 HTTP 요청을 보낼 때, CORS 정책이 적용되지 않으면 요청에 실패한다.
const cors = require('cors');
app.use(cors({
origin: 'http://localhost:3000',
credentials: true,
}));
3. React.forwardRef
React.forwardRef 는 리액트에서 ref 를 컴포넌트 외부로 전달해줄 수 있게 해주는 API 이다.
기본적으로 React 컴포넌트는 ref 를 내부에서만 사용할 수 있지만, forwardRef 를 사용하면 부모 컴포넌트에서 자식 컴포넌트의 DOM 요소를 참조할 수 있게 된다.
3.1. ref 란?
ref 는 리액트에서 DOM 요소나 리액트 컴포넌트를 참조하는 가상의 포인터라고 생각하면 된다. ref 를 사용하면 해당 요소나 컴포넌트의 인스턴스를 직접 조작하거나 접근할 수 있다. useRef 를 사용해서 ref 객체를 생성할 수 있다.
ref 는 컴포넌트 내부에서만 사용이 가능하고, useRef 로 저장된 값이 변경되어도 컴포넌트에 렌더링이 일어나지 않는다.
input 에 ref 를 설정하여 해당 요소를 참조할 수 있게 하였다. inputRef.current 를 사용하여 input 에 대한 접근이 가능하다.
(inputRef.current 는 input 요소를 가리킨다)
처음에 ref 객체가 가리키는 값은 null 이지만 ref={inputRef} 로 input 요소를 참조하게 하였다.
const inputRef = useRef<HTMLInputElement>(null);
<input ref={inputRef} />
3.2. forwardRef 사용하기
3.1. 에서 설명한 것처럼 리액트에서는 원래 부모 컴포넌트가 자식 컴포넌트의 DOM 요소에 직접 접근할 수 없지만, forwardRef 를 사용하면 부모가 자식 컴포넌트의 ref 를 통해 자식 컴포넌트의 DOM 요소에 접근할 수 있다.
자식 컴포넌트는 forwardRef 로 감싸져 있어서 부모 컴포넌트가 ref 를 전달할 수 있다.
// 자식 컴포넌트
import React, { forwardRef } from 'react';
// 자식 컴포넌트는 forwardRef로 ref를 받는다
const MyInput = forwardRef<HTMLInputElement, { placeholder: string }>((props, ref) => {
return <input ref={ref} placeholder={props.placeholder} />;
});
export default MyInput;
// 부모 컴포넌트
import React, { useRef } from 'react';
import MyInput from './MyInput';
const ParentComponent = () => {
const inputRef = useRef<HTMLInputElement>(null); // input을 참조할 ref 생성
const focusInput = () => {
if (inputRef.current) {
inputRef.current.focus(); // ref를 통해 input에 포커스
}
};
return (
<div>
<MyInput ref={inputRef} placeholder="Enter text here" />
<button onClick={focusInput}>Focus on Input</button>
</div>
);
};
export default ParentComponent;
4. 자잘자잘 몰랐던 것들
4.1. useCallback
리액트의 훅으로 함수의 메모제이션(Memoization)을 제공하여 불필요한 재생성을 방지하는 데 사용된다. 이를 통해서 컴포넌트가 재렌더링될 때마다 함수가 새로 생성되는 문제를 방지할 수 있다. ( == 필요할 때만 함수를 생성해서 사용하는 것)
첫번째 인자로 함수를 받고 두번째 인자로 의존성 배열 deps 를 받는다. 인자로 넣은 배열에 변화가 있을 때만 함수를 새로 생성하고, 의존성 배열의 값이 변하지 않으면 이전의 함수를 재사용한다.
const memoizedCallback = useCallback(() => {
// ...
}, [dependencies]);
4.2. React.InputHTMLAttributes<HTMLInputElement>
React.InputHTMLAttributes<HTMLInputElement> 는 input 요소에서 사용할 수 있는 모든 기본 HTML 속성들을 포함하는 타입으로, input 에 기본적으로 적용할 수 있는 속성들을 타입스크립트 타입으로 정의해둔 것이다.
interface Props extends React.InputHTMLAttributes<HTMLInputElement>{
placeholder?: string;
inputType?: "text" | "email" | "password" | "number";
}
const InputText = React.forwardRef((
{placeholder, inputType, onChange, ...props}: Props,
ref: ForwardedRef<HTMLInputElement>) => {
return (
<InputTextStyled
placeholder={placeholder}
ref={ref} type={inputType}
{...props} onChange={onChange}
/>
);
})
예를 들어, value, placeholder, onChange, type .. 등의 다양한 HTML 속성들을 쓰려면 인터페이스에 하나하나 다 정의해둬야하지만,
InputHTMLAttributes 를 붙여서 확장하면 자동으로 다 포함시킬 수 있기 때문에 위의 코드처럼 편리하게 사용할 수 있다.
'TIL with Programmers' 카테고리의 다른 글
[TIL] 11/15 -webkit-box, Ellipsis, $ 접두사, 낙관적 업데이트 (0) | 2024.11.15 |
---|---|
[TIL] 11/14 - Zustand, useSearchParams, URLSearchParams, useLocation, 동적 CSS (2) | 2024.11.14 |
[TIL] 11/12 styled-components 모르는 것 정리 (0) | 2024.11.12 |
[TIL] 11/11 React context API, 테마 스위쳐 구현, 모르는 것 정리 (1) | 2024.11.11 |
[TIL] 11/8 React.ts 살펴보기 (0) | 2024.11.08 |