본문 바로가기
TIL with Programmers

[TIL] 11/13 회원가입, useForm, http 클라이언트 생성, forwardRef

by 보먀 2024. 11. 13.
728x90
반응형

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 를 붙여서 확장하면 자동으로 다 포함시킬 수 있기 때문에 위의 코드처럼 편리하게 사용할 수 있다. 

728x90
반응형