[패스트캠퍼스 수강 후기] 프론트엔드 인강 100% 환급 챌린지 40회차 미션 - 33강 useReducer, useAsync, 커스텀 Hook

5 minute read

useReducer로 요청 상태 관리하기

이전에 썼던 코드를 useReducer로 다시 만들어보자. 처음에는 코드가 좀 길어지는 것 같기도 할것이다. 그치만 이렇게하면 요청관리에 대한 파일을 따로 관리해서 재사용 할 수 있다는 장점이 있다. 일단 useState로 썼던 코드를 지우자. 그리고 아래와 같이 수정해준다.

Users.js

import React, { useEffect, useReducer } from "react";
import axios from "axios";

// LOADING, SUCCESS, ERROR
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 Users() {
  const [state, dispatch] = useReducer(reducer, {
    loading: false,
    data: null,
    error: null,
  });
  useEffect(() => {
    const fetchUsers = async () => {
      dispatch({ type: "LOADING" });
      try {
        const response = await axios.get(
          "https://jsonplaceholder.typicode.com/users/"
        );
        dispatch({ type: "SUCCESS", data: response.data });
      } catch (e) {
        dispatch({ type: "Error", error: e });
      }
    };
    fetchUsers();
  }, []);

  const { loading, data: users, error } = state;
  if (loading) return <div>로딩중..</div>;
  if (error) return <div>에러가 발생했습니다. </div>;
  if (!users) return null;
  return (
    <ul>
      {users.map((user) => (
        <li key={user.id}>
          {user.username} ({user.name})
        </li>
      ))}
    </ul>
  );
}
export default Users;

이렇게 reducer를 상단에 함수로 따로 쓸 수 있다. 그리고 VSC의 기능을 이용하면 바로 파일 분리도 가능하다. 먼저 reducer의 이름을 acyncReducer로 바꾸고 이름부분을 오른클릭 해보자. ‘Refactor…‘을 누르고 ‘Move to new file’을 하면 파일이 분리되어 새로 생성된다(유레카!) 이렇게 할 수 있다는 걸 알아두고 일단 원상 복귀한다.

useAsync 커스텀 Hook 만들어서 사용하기

useAsync.js

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

// LOADING, SUCCESS, ERROR
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 = []) {
  //export를 이렇게 할 수도 있음
  //deps dependency
  const [state, dispatch] = useReducer(reducer, {
    loading: false,
    data: null,
    error: null,
  });

  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-disable-next-line
  }, deps);

  return [state, fetchData];
}
export default useAsync;

그리고 Users.js는 아래와 같이 바꿔보자.

Users.js

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

async function getUsers() {
  const response = await axios.get(
    "https://jsonplaceholder.typicode.com/users"
  );
  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;

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

지금의 경우에는 처음 렌더링 되는 시점에만 데이터 요청이 이루어지고 있는데, 만약에 컴포넌트가 처음 렌더링 될때는 요청하지 않고 특정 버튼을 눌러야지만 요청을 시작하고 싶다면 어떻게 하는지 알아보자.
글을 작성한다던지 수정한다던지 등 사용자의 특정 인터렉션이 있는 경우에만 발생하는게 일반적이기 때문이다.

useAsync.js

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

// LOADING, SUCCESS, ERROR
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 = [], skip = false) {
  //skip 추가
  const [state, dispatch] = useReducer(reducer, {
    loading: false,
    data: null,
    error: null,
  });

  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) {
      //skip 추가
      return;
    }
    fetchData();
  }, deps);

  return [state, fetchData];
}

export default useAsync;

Users.js

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

async function getUsers() {
  const response = await axios.get(
    "https://jsonplaceholder.typicode.com/users"
  );
  return response.data;
}

function Users() {
  const [state, refetch] = useAsync(getUsers, [], true); //세번째 파라미터인 skip = true

  const { loading, data: users, error } = state;
  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}>
            {user.username} ({user.name})
          </li>
        ))}
      </ul>
      <button onClick={refetch}>다시 불러오기</button>
    </>
  );
}

export default Users;

그럼 아래와 같이 ‘불러오기’를 한 다음에 리스트를 불러오는 것을 볼 수 있다.
리액트화면 리액트화면

이번에는 API에서 특정 아이디를 조회해서 가져오는 것을 해보자.
우선 새로운 컴포넌트 User.js를 만들어준다.

User.js

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

async function getUser(id) {
  const response = await axios.get(
    `http://jsonplaceholder.typicode.com/users/${id}`
  );
  return response.data;
}

function User({ id }) {
  const [state] = useAsync(() => getUser(id), [id]);
  const { loading, data: user, error } = state;

  if (loading) return <div>로딩중...</div>;
  if (error) return <div>에러가 발생했습니다.</div>;
  if (!user) return null;
  return (
    <div>
      <h2>{user.username}</h2>
      <p>
        <b>Email:</b> {user.email}
      </p>
    </div>
  );
}

export default User;

그리고 Users를 수정해보자.

Users.js

import React, { useState } from "react";
import axios from "axios";
import useAsync from "./useAsync";
import User from "./User"; //User 추가

async function getUsers() {
  const response = await axios.get("http://jsonplaceholder.typicode.com/users");
  return response.data;
}

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

  const { loading, data: users, error } = state;
  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}
            //li를 클릭하면 User 보이도록 추가
            onClick={() => setUserId(user.id)}
          >
            {user.username} ({user.name})
          </li>
        ))}
      </ul>
      <button onClick={refetch}>다시 불러오기</button>
      {userId && (
        <User
          //User컴포넌트 불러와서 User의 아이디를 가져와줌
          id={userId}
        />
      )}
    </>
  );
}

export default Users;

리액트화면 우리가 만약에 요청상태를 관리해야할때마다 useState 세개씩 만들어야 했다면 굉장히 귀찮았을 것이다. 렌더링 될때 데이터를 요청해야하는 상황에만 userAsync Hook을 만들어서 useEffect도 대신 해주게끔 사용하면 코드를 많이 아낄 수 있을 것이다.
그리고 fetchData도 Hook으로 만들어서 써주어서 매번 안만들어 줘도 되게끔 해보았다.
이제 이 Hook이 어떻게 만들었는지 배웠으니 개인의 용도에 맞춰서 계속 활용해보자!

오늘은 여기까지..
시청 영상 33강 03~04까지

수강인증이미지 프론트엔드 개발 올인원 패키지 with React Online. 👉 https://bit.ly/31Cf1hp

Comments