본문 바로가기

STUDY/React

React | custom hook (useAsync 훅 만들기)

Custom Hook?

  • 리액트가 제공하는 hooks를 이용해 커스텀 훅을 만들 수 있다
  • 커스텀 훅은 로직을 재사용 할 수 있다
  • 커스텀 훅의 이름은 리액트 내장 훅처럼 use로 시작하는 것이 좋다

A custom Hook is a JavaScript function whose name starts with use and that may call other Hooks.
출처: https://reactjs.org/docs/hooks-custom.html

useAsync hook

많이들 만들어 사용하는 커스텀 훅 중에 하나인 useAsync 훅을 만들어본다
API 요청에 유용하게 사용된다!

일단 useReduceruseEffect, useCallback을 import 한다.

import { useReducer, useEffect, useCallback } from 'react';

useReducer를 사용하기 위해 reducer를 작성해준다.
initailState는 이렇게 따로 선언해도 좋고, useReduer의 두 번째 인자 값에 바로 작성해도 좋다.

loading은 api요청의 응답을 받기 전까지 true를 유지한다.
data는 api요청의 응답 값 이다.
error는 api요청에 실패했을 경우 에러를 저장한다.

const initialState = {
  loading: false,
  data: null,
  error: false,
};

const reducer = (state, action) => {
  switch (action.type) {
    case 'LOADING':
      return {
        loading: true,
        data: null,
        error: null,
      };

    case 'SUCCESS':
      return {
        loading: false,
        data: action.data,
        error: null,
      };

    case 'ERROR':
      return {
        loading: false,
        data: null,
        error: action.error,
      };

    case 'CLEAR':
      return initialState;

    default:
      return state;
  }
};

그리고 본격적으로 useAsync 훅을 작성

인자 값으로 받는 callback은 api요청을 할 함수를,

immediate는 해당 함수를 즉시 실행할지 여부를 전달받는다. 기본값은 false로 설정.

useCallback는 함수를 재사용 할 수 있는 리액트에서 제공하는 내장 훅이다. 의존성 배열의 callback값의 변경 여부를 확인한다.

const useAsync = (callback, immediate = false) => {

  const [state, dispatch] = useReducer(reducer, initialState);

  const execute = useCallback(
    async (...args) => {
      dispatch({ type: 'LOADING' });

      try {
        const data = await callback(options, ...args);
        dispatch({ type: 'SUCCESS', data });
        return true;
      } catch (e) {
        dispatch({ type: 'ERROR', error: e });
      }
    },
    [callback]
  );

  useEffect(() => {
    immediate && execute();
    return () => dispatch({ type: 'CLEAR' });
  }, [immediate, execute]);

  return { ...state, execute };
};

export default useAsync;

return [state, execute]로 반환할 수도 있다. (취향차이)

사용해보기!

로그인을 한다고 할 때.. 로그인을 위한 서버에 요청하는 부분을 useAsync를 사용해보자.

리액트와 api요청을 위한 axios, 그리고 방금 작성한 useAsync훅을 import한다.

import React, { useState } from 'react';
import axios from 'axios';
import useAsync from '../hooks/useAsync';

아까 useAsync훅에서 {...state, execute }를 반환해주었기 때문에,

{ loading, error, execue: login }으로 받을 수 있다.

execute: login은 execute대신 login으로 사용하겠다.. 뭐 이렇게 이해하면 된다..

// try/catch 생략..
const apiRequesLogin = async (data) => {
    const response = await axios.post('http://...(생략)');
    return response.data;
}

const LoginForm = () => {
  const { loading, error, data, execute: login } = useAsync(apiRequesLogin);

  const [formInput, setFromInput] = useState({
    id: '',
    password: '',
  });
  const { account, password } = formInput;

  const onChange = (e) => {
    setFromInput({
      ...formInput,
      [e.target.name]: e.target.value,
    });
  };

  const onSubmit = async (e) => {
    e.preventDefault();

    const result = await login(formInput);
    console.log("apiRequestLogin response.data = ", data);
    if (result) {
      // 나중엔 이렇게 react-router-dom의 history를 이용해서 페이지를 이동해주면 된다.
      // history.push('/user');
    }
  };

  if (loading) {
    return <h1>loading...</h1>;
  }
  if (error) {
    return <h1>{error}</h1>;
  }

  return (
    <form onSubmit={onSubmit}>
      <input name="id" type="text" value={id} onChange={onChange} />
      <input
        name="password"
        type="password"
        value={password}
        onChange={onChange}
      />
      <button type="submit">LOGIN</button>
    </form>
  );
};

export default LoginForm;

참고링크