벨로퍼트 리액트 - 4. API 연동하기
4-3. react-async 로 요청 상태 관리하기
react-async
: 라이브러리. 프로젝트를 생성할때마다 직접 요청 상태 관리를 위한 커스텀 Hook을 만들기 귀찮다면 사용할 수 있다. 단, 직접 커스텀 Hook을 생성할때는 결과물을 배열로 반환하지만 react-async의 Hook은 객체 형태로 반환한다.
react-async 의 useAsync
를 사용할때는 파라미터로 넣은 옵션 객체에 호출할 함수 promiseFn
을 넣고 파라미터도 필드 이름과 함께 (customerId
) 넣어주어야 한다.
User.js
import React from "react";
import axios from "axios";
import useAsync from 'react-async';
async function getUser({id}) {
const response = await axios.get(
`https://jsonplaceholder.typicode.com/users/${id}`
);
return response.data;
}
function User({id}) { // useAsync 사용시 프로미스를 반환하는 함수의 파라미터를 객체 형태로 해주어야 한다
const { data: user, error, isLoading } = useAsync({
promiseFn: getUser,
id,
watch: id // 특정값을 넣으면 값이 바뀔때마다 promiseFn에 넣은 함수를 다시 호출해준다.
});
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';
import User from './User';
// useAsync 에서는 Promise 의 결과를 바로 data 에 담기 때문에,
// 요청을 한 이후 response 에서 data 추출하여 반환하는 함수를 따로 만들었습니다.
async function getUsers() {
const response = await axios.get(
'https://jsonplaceholder.typicode.com/users'
)
return response.data;
}
function Users() {
const [userId, setUserId] = useState(null); // 클릭된 id 상태 관리
// const { data: users, error, isLoading, run } = useAsync({
const { data: users, error, isLoading, reload } = useAsync({
promiseFn: getUsers,
// deferFn: getUsers // 특정 인터렉션에 따라 API를 호출하고 싶을때 deferFn, run을 사용
});
if (isLoading) return <div>로딩중...</div>; // 로딩상태 활성화
if (error) return <div>에러가 발생했습니다</div>;
if (!users) return <button onClick={reload}>불러오기</button>; // users 값이 아직 없을때
// if (!users) return <button onClick={run}>불러오기</button>; // users 값이 아직 없을때
return (
<>
<ul>
{users.map(user => (
<li
key={user.id}
onClick={() => setUserId(user.id)} // 클릭시 이 id로 userId 상태 변경
style={{cursor:'pointer'}}
>
{user.username} ({user.name})
</li>
))}
</ul>
<button onClick={reload}>다시 불러오기</button>
{/* <button onClick={run}>다시 불러오기</button> */}
{userId && <User id={userId}/>}
</>
);
}
export default Users;
4-5. context와 함께 사용하기
컴포넌트에서 필요한 외부 데이터들은 컴포넌트 내부에서 useAsync 같은 Hook을 사용해서 작업을 하면 충분하지만 특정 데이터들이 다른 여러 컴포넌트에서 필요하게 될 때도 있다. 그럴 때에는 Context를 사용하면 개발이 편하다.
UsersContext.js 만들어서 사용하기
import React, { createContext, useReducer, useContext } from "react";
import axios from "axios";
// UsersContext 에서 사용할 기본 상태
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 => ({
loading: false,
data,
error: null
});
// 실패했을때의 상태 만들어주는 함수
const error = error => ({
loading: false,
data: null,
error: 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(`Unhanded action type: ${action.type}`);
}
}
// State 용 Context 와 Dispatch 용 Context 따로 만들어주기
const UsersStateContext = createContext();
const UsersDispatchContext = createContext();
// 위에서 선언한 두가지 Context 들을 Provider로 감싸주는 컴포넌트
export function UsersProvider({ children }) {
const [state, dispatch] = useReducer(usersReducer, initialState);
return (
<UsersStateContext.Provider value={state}>
<UsersDispatchContext.Provider value={dispatch}>
{ children }
</UsersDispatchContext.Provider>
</UsersStateContext.Provider>
);
}
// State를 쉽게 조회할 수 있게 해주는 커스텀 Hook
export function useUsersState() {
const state = useContext(UsersStateContext);
if (!state) {
throw new Error('Cannot find UsersProvider');
}
return state;
}
// Dispatch를 쉽게 사용할 수 있게 해주는 커스텀 Hook
export function useUsersDispatch() {
const state = useContext(UsersDispatchContext);
if (!state) {
throw new Error('Cannot find UsersProvider');
}
return state;
}
// API 처리 함수 만들기. dispatch와 API에 필요한 파라미터를 받아옴
export async function getUsers(dispatch) {
dispatch({type: 'GET_USERS'});
try {
const response = await axios.get(
'https://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(
`https://jsonplaceholder.typicode.com/users/${id}`
);
dispatch({type: 'GET_USER_SUCCESS', data: response.data});
} catch (e) {
dispatch({type: 'GET_USER_ERROR', error: e});
}
}
Context를 사용하기
App.js
import Users from './Users';
import { UsersProvider } from './UsersContext';
function App() {
return (
<UsersProvider>
<Users />
</UsersProvider>
);
}
export default App;
Users.js
// Context 사용시 변경되는 코드
import React, { useState } from "react";
import { getUsers, useUsersDispatch, useUsersState } from "./UsersContext";
import User from "./User";
function Users() {
const [userId, setUserId] = useState(null);
const state = useUsersState();
const dispatch = useUsersDispatch();
const {data: users, loading, 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)}
style={{cursor:'pointer'}}>
{user.username} ({user.name})
</li>
))}
<button onClick={fetchData}>다시 불러오기</button>
{userId && <User id={userId} />}
</ul>
</>
);
}
export default Users;
User.js
// Context 사용시 변경되는 코드
import React, { useEffect } from "react";
import { getUser, useUsersDispatch, useUsersState } from "./UsersContext";
function User({ id }) {
const state = useUsersState();
const dispatch = useUsersDispatch();
useEffect(()=>{
getUser(dispatch, id);
}, [dispatch, id]); // id값이 바뀔때마다 getUser를 호출한다.
const {data: user, loading, 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;