React/API 연동

[React] #4 | useAsync 커스텀 Hook 만들어서 사용하기

yevdev 2021. 11. 3. 13:44

데이터를 요청할때마다 리듀서를 작성하는건 번거롭다.

매번 반복되는 코드를 작성하는 대신에, 커스텀 Hook을 만들어서 요청 상태 관리 로직을 쉽게 재사용하는 방법을 알아보자.

 

useAsync.js

import { useReducer, useEffect } from 'react'

function 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
        };
      default:
        throw new Error(`Unhandled action type: ${action.type}`);
    }
  }
  
  function useAsync(callback, deps = []) {
    const [state, dispatch] = useReducer(reducer, {
      loading: false,
      data: null,
      error: false
    });
  
    const fetchData = async () => {
      dispatch({ type: 'LOADING' });
      try {
        const data = await callback();
        dispatch({ type: 'SUCCESS', data });
      } catch (e) {
        dispatch({ type: 'ERROR', error: e });
      }
    };
  
    useEffect(() => {
      fetchData();
      // eslint 설정을 다음 줄에서만 비활성화
      // eslint-disable-next-line
    }, deps);
  
    return [state, fetchData];
  }
  
  export default useAsync;

💡useAsync?

  • 두가지 파라미터를 받아옴
    1. API 요청을 시작하는 함수
    2. deps : 해당 함수 안에서 사용하는 useEffect의 deps
      • deps는 나중에 우리가 사용할 비동기 함수에서 파라미터가 필요하고, 그 파라미터가 바뀔 때 새로운 데이터를 불러오고 싶은 경우에 활용
      • 기본값은 [] -> 즉, 컴포넌트가 가장 처음 렌더링 할 때만 API를 호출하고 싶다는 의미
  • 이 Hook 에서 반환하는 값 : 요청관련 상태fetchData 함수 -> 나중에 데이터를 쉽게 리로딩

 

 

➰만든 Hook을 사용하기

Users.js

import React from 'react'
import axios from 'axios';
import useAsync from './useAsync';

//useAsync 에서는 Promise의 결과를 바로 Data에 담기 때문에,
//요청을 한 이후 response에서 data를 추출하여 반환하는 함수를 따로 만든다.
async function getUsers(){
    const response = await axios.get(
        'api주소'
    );
    return response.data;
}

function Users() {

    const [state, refetch] = useAsync(getUsers, []);

    const {loading, data:users, error} = state;

    if (loading) return <div>로딩중..</div>     // 로딩이 활성화되었을 때
    if (error) return <div>에러가 발생했습니다.</div>
    if (!users) return null;    //user 값이 아직 없을 때

    return (
        <>
        <ul>
            {users.map(user=>{
                <li key={user.id}>
                    {user.username} ({user.name});
                </li>
            })}
        </ul>
        <button onClick={refetch}>다시 불러오기</button>
        </>
    )
}

export default Users

코드도 훨씬 깔끔해지고, 재사용하기 편리해짐!

 


데이터 나중에 불러오기

  • Users 컴포넌트는 컴포넌트가 처음 렌더링 되는 시점부터 API를 요청하고 있다.
  • 만약, 특정 버튼을 눌렀을 때만 API를 요청하고 싶다면?
    • POST, DELETE, PUT PATCH 등의 HTTP

 

구현

  • useAsync에 skip 파라미터를 추가
  • skip 파라미터의 기본 값을 false로 지정
  • 이 값이 true라면 useEffect에서 아무런 작업도 하지 않도록 설정

useAsync.js

function useAsync(callback, deps = [], skip=false) {
    const [state, dispatch] = useReducer(reducer, {
      loading: false,
      data: null,
      error: false
    });
  
    const fetchData = async () => {
      dispatch({ type: 'LOADING' });
      try {
        const data = await callback();
        dispatch({ type: 'SUCCESS', data });
      } catch (e) {
        dispatch({ type: 'ERROR', error: e });
      }
    };
  
    useEffect(() => {
        if (skip) return;
        fetchData();
        // eslint 설정을 다음 줄에서만 비활성화
        // eslint-disable-next-line
    }, deps);
  
    return [state, fetchData];
  }

Users.js

function Users() {

    const [state, refetch] = useAsync(getUsers, [], true);

    const {loading, data:users, error} = state;

    if (loading) return <div>로딩중..</div>     // 로딩이 활성화되었을 때
    if (error) return <div>에러가 발생했습니다.</div>
    if (!users) return <button onClick={refetch}>불러오기</button>;    //user 값이 아직 없을 때

    return (
        <>
        <ul>
            {users.map(user=>{
                <li key={user.id}>
                    {user.username} ({user.name});
                </li>
            })}
        </ul>
        <button onClick={refetch}>다시 불러오기</button>
        </>
    )
}
  • useAsync의 세번째 파라미터에 true를 넣어줬고, !users 인 상황에 불러오기 버튼을 렌더링

 


API 에 파라미터가 필요할 경우

  • id를 props로 받아와서 
  • https:// ..... /users/1 이런식으로 맨뒤에 id를 넣어서 API 요청을 해보자.

User.js

import React from 'react'
import axios from 'axios';
import useAsync from './useAsync';

async function getUser(id){
    const response = await axios.get(
        'api주소/${id}'
    );
    return response.data;
}

function User({ id }) {

    const [state] = useAsync(()=> getUser(id), [id]);

    const {loading, data:users, error} = state;

    if (loading) return <div>로딩중..</div>     // 로딩이 활성화되었을 때
    if (error) return <div>에러가 발생했습니다.</div>
    if (!users) return null;

    return (
        <div>
            <h2>{user.username}</h2>
            <p>
                <b>Email:</b> {user.email}
            </p>
        </div>
    )
}

export default User
  • useAsync를 사용할 때, 파라미터를 포함시켜서 함수를 호출하는 새로운 함수를 만들어서 등록
  • id가 바뀔 때마다 재호출되도록 deps에 id를 넣어줌

 

  • 그다믕, Users.js에서 useState를 사용하여 userId 상태를 관리해보겠다.
  • 초깃값은 null , 리스트에 있는 항목을 클릭하면 클릭한 사용자의 id를 userId 값으로 설정해준다.

Users.js

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

// useAsync 에서는 Promise 의 결과를 바로 data 에 담기 때문에,
// 요청을 한 이후 response 에서 data 추출하여 반환하는 함수를 따로 만들었습니다.
async function getUsers() {
  const response = await axios.get(
    'api 주소'
  );
  return response.data;
}

function Users() {
  const [userId, setUserId] = useState(null);   //userId 상태관리
  const [state, refetch] = useAsync(getUsers, [], true);

  const { loading, data: users, error } = state; // state.data 를 users 키워드로 조회

  if (loading) return <div>로딩중..</div>;
  if (error) return <div>에러가 발생했습니다</div>;
  if (!users) return <button onClick={refetch}>불러오기</button>;
  return (
    <>
      <ul>
        {users.map(user => (
          <li
            key={user.id}
            onClick={() => setUserId(user.id)}
            style={{ cursor: 'pointer' }}
          >
            {user.username} ({user.name})
          </li>
        ))}
      </ul>
      <button onClick={refetch}>다시 불러오기</button>
      {userId && <User id={userId} />}
    </>
  );
}

export default Users;

 


Reference

벨로퍼트의 모던 리액트