[패스트캠퍼스 수강 후기] 프론트엔드 인강 100% 환급 챌린지 41회차 미션 - 33강 react-async, Context에서 비동기작업 상태관리

4 minute read

react-async

매번 훅을 만드는게 익숙하고 편하다면 그냥 그렇게 해도 되겠지만 매번 프로젝트마다 그렇게 하는게 힘들다면 react-async를 사용하면 된다. 이 라이브러리에는 많은 기능이 내장되어있다. 가장 기본적인 기능은 useAsync이다. 기존에 만들었던 코드를 react-async로 바꿔보도록 하자.

User.js

import React from "react";
import axios from "axios";
import { useAsync } from "react-async"; //react-async로 수정

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

function User({ id }) {
  const { data: user, error, isLoading } = useAsync({
    //react-async로 수정
    promiseFn: getUser,
    id,
    watch: id,
  });

  if (isLoading) 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.js

import React, { useState } from "react";
import axios from "axios";
import { useAsync } from "react-async"; //react-async로 수정
import User from "./User";

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

function Users() {
  const [userId, setUserId] = useState(null);
  const { data: users, error, isLoading, reload } = useAsync({
    //react-async로 수정
    promiseFn: getUsers,
  });

  if (isLoading) return <div>로딩중..</div>;
  if (error) return <div>에러가 발생했습니다. </div>;
  if (!users) return <button onClick={reload}>불러오기</button>;

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

export default Users;

이렇게 하고 시작해보면 전체 리스트를 불러오고 li클릭시 user정보를 가져오는 것을 볼 수 있다.
그럼 여기서 이전처럼 리스트를 바로 불러오지 않고 특정 버튼을 누를 시 불러오게 해보자.

Users.js

import React, { useState } from "react";
import axios from "axios";
import { useAsync } from "react-async";
import User from "./User";

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

function Users() {
  const [userId, setUserId] = useState(null);
  const { data: users, error, isLoading, reload, run } = useAsync({
    // run추가
    deferFn: getUsers,
  });

  if (isLoading) return <div>로딩중..</div>;
  if (error) return <div>에러가 발생했습니다. </div>;
  if (!users) return <button onClick={run}>불러오기</button>; // run추가

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

export default Users;

장점 및 단점

장점으로는 필요할때 설치해서 바로 사용할 수 있어서 간편해서 좋다. 훅 말고 컴포넌트 형태로도 사용할 수 있기도 하다. 특정 프로미스를 기다리는 작업을 도중에 취소할 수 있는 기능도 있다.
반면 단점은 옵션이 다양하고 복잡하다. 그래서 처음에 조금 헷갈릴 수도 있다. 만약 커스텀 훅을 만드는게 별로 어렵게 느껴지지 않는다면 직접 만들어서 쓰는 것도 나쁘지 않은 선택이다.

Context에서 비동기작업 상태 관리하기

로그인 중인 사용자의 정보를 체크하는 작업을 한다면 다양한 곳에서 사용자의 로그인 상태를 필요로 할 수 있다. 여기저기서 필요 한 상태를 비동기적으로 가져와서 사용해야 하는 경우엔 데이터를 Context에 넣어줘서 작업하면 편하다. 만약 Context 내부에서 비동기작업을 위한 상태관리를 한다면 어떻게하는게 효율적인지 알아보자.

UsersContext.js

import React, { createContext, useReducer, useContext } from "react";
import axios from "axios";

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

const loadingState = {
  loading: true,
  data: null,
  error: null,
};

const success = (data) => ({
  lading: false,
  data,
  error: null,
});
const error = (e) => ({
  loading: false,
  data: null,
  error: e,
});

// GET_USERS
// GET_USERS_SUCCESS
// GET_USERS_ERROR
// GET_USER
// GET_USER_SUCCESS
// GET_USER_ERROR

function usersReducer(state, action) {
  switch (action.type) {
    case "GET_USERS":
      return {
        ...state,
        users: loadingState,
      };
    case "GET_USERS_SUCCESS":
      return {
        ...state,
        users: success(action.data),
      };
    case "GET_USERS_ERROR":
      return {
        ...state,
        users: error(action.error),
      };
    case "GET_USER":
      return {
        ...state,
        user: loadingState,
      };
    case "GET_USER_SUCCESS":
      return {
        ...state,
        user: success(action.data),
      };
    case "GET_USER_ERROR":
      return {
        ...state,
        user: error(action.error),
      };
    default:
      throw new Error("Unhandled action type", action.type);
  }
}

const UsersStateContext = createContext(null);
const UsersDispatchContext = createContext(null);

export function UsersProvider({ children }) {
  const [state, dispatch] = useReducer(usersReducer, initialState);
  return (
    <UsersStateContext.Provider value={state}>
      <UsersDispatchContext.Provider value={dispatch}>
        {children}
      </UsersDispatchContext.Provider>
    </UsersStateContext.Provider>
  );
}

export function useUsersState() {
  const state = useContext(UsersStateContext);
  if (!state) {
    throw new Error("Cannot find UserProvider");
  }
  return state;
}

export function useUsersDispatch() {
  const dispatch = useContext(UsersDispatchContext);
  if (!dispatch) {
    throw new Error("Cannot find UserProvider");
  }
  return dispatch;
}

export async function getUsers(dispatch) {
  dispatch({ type: "GET_USERS" });
  try {
    const response = await axios.get(
      "http://jsonplaceholder.typicode.com/users"
    );
    dispatch({
      type: "GET_USERS_SUCCESS",
      data: response.data,
    });
  } catch (e) {
    dispatch({
      type: "GET_USERS_ERROR",
      error: e,
    });
  }
}

export async function getUser(dispatch, id) {
  dispatch({ type: "GET_USER" });
  try {
    const response = await axios.get(
      `http://jsonplaceholder.typicode.com/users/${id}`
    );
    dispatch({
      type: "GET_USER_SUCCESS",
      data: response.data,
    });
  } catch (e) {
    dispatch({
      type: "GET_USER_ERROR",
      error: e,
    });
  }
}

User.js

import React, { useEffect } from "react";
import { getUser, useUsersState, useUsersDispatch } from "./UsersContext";

function User({ id }) {
  const state = useUsersState();
  const dispatch = useUsersDispatch();

  useEffect(() => {
    getUser(dispatch, id);
  }, [dispatch, id]);
  const { loading, data: user, error } = state.user;

  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.js

import React, { useState } from "react";
import { useUsersState, useUsersDispatch, getUsers } from "./UsersContext";
import User from "./User";

function Users() {
  const [userId, setUserId] = useState(null);
  const state = useUsersState();
  const dispatch = useUsersDispatch();

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

  const fetchData = () => {
    getUsers(dispatch);
  };
  if (loading) return <div>로딩중..</div>;
  if (error) return <div>에러가 발생했습니다. </div>;
  if (!users) return <button onClick={fetchData}>불러오기</button>;

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

export default Users;

이와 같이 Context로 완료해봤다.. 근데 왜한건지 잘 모르겠다..;
아무래도 javascript의 기본이 제대로 안되어있어서 이해가 잘 안되는가보다 ㅜㅜ
Promise, await, async 다시 공부하고 와야겠다.
오늘은 여기까지..
시청 영상 33강 05~06까지
수강인증이미지 수강인증이미지 수강인증이미지 프론트엔드 개발 올인원 패키지 with React Online. 👉 https://bit.ly/31Cf1hp

Comments