useReducer로 요청 상태 관리하기

정영찬·2022년 3월 5일
1

리액트

목록 보기
41/79

전에 실습을 useState대신에 useReducer를 사용해서 상태를 관리해보자.

reducer는 3가지 액션을 관리한다.(LOADING,SUCCESS,ERROR)

LOADING은 loading 값을 true로 변경, SUCCESS는 loding값을 false로 변경하고 data값을 action.data로 변경한다. ERROR는 loading을 false, data는 null값으로하고, error를 action.error로 변경한다.


//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}`);
    }
}

useReducer를 생성한다.

 const [state, dispatch] = useReducer(reducer, {
        loading:false,
        data: null,
        error:null,
    });

fetchUsers 함수를 수정한다.

 const fetchUsers = async () => {
        dispatch({type: 'LOADING'});
        try{
            /* setUsers(null);
            setError(null);
            setLoading(true); */
            const response = await axios.get(
                'https://jsonplaceholder.typicode.com/users/'
            )
           dispatch({type: 'SUCCESS', data: response.data});
        } catch (e) {
            console.log(e.response.status)
            dispatch({ type: 'ERROR', error: e});
        }
    };

useEffect도 수정한다. reducer 초기 데이터중에서는 users가 없으므로 data를 users로 선언해주는 작업을 진행한다.


    useEffect(() => {
        fetchUsers();
    }, []);
    const {loading, data:users, error} = state;
    if(loading) return <div>로딩중</div>
    if(error) return <div>에러 발생</div>
    if(!users) return null;

전보다는 파일의 내용이 많아졌지만, reducer를 다른 파일만들어서 불러오는 방식으로 사용하면, 다른 컴포넌트가 reducer를 필요로 할때 불러와서 사용할 수 있는 장점이 있다.

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

커스텀 Hook을 만들어서 요청상태 관리 로직을 쉽게 재사용하는 방법이 있다.
useAsync라는 파일을 생성하여 코드를 작성한다. 반환되는 값은 state와 fetchData함수이며, fetchData 함수를 반환해서 나중에 데이터를 쉽게 리로딩을 해줄수 있게 된다.

  • Users에서 작성한 reducer를 복사하여 가져온다.
  • useAsync 함수 내부에 fetchData함수를 작성한다.
function useAsync(callback, deps =[]){
    const [state, dispatch] = useReducer(reducer, {
        laoding: false,
        data: null,
        error: null
    });

    const fetchData = useCallback(async() =>{
        dispatch({type: 'LOADING'});
        try{
            const data =await callback();
            dispatch({type: 'SUCCESS', data});
        } catch(e) {
            dispatch({type: 'ERROR', error: e})
        }
    },[callback]);

    useEffect(() => {
        fetchData();
        //eslint-disable-next-line
    },deps);

    return [state,fetchData];
}

export default useAsync;

이제 Users에 작성한 reducer와 fetchUsers, useEffect,useReducer의 내용을 모두 제거한다.

get메서드를 사용해서 데이터를 리턴해주는 getUsers 비동기 함수를 작성한다.

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

Users 함수 내부에 useAsync를 사용한다.

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.name})
                </li>
            ))}
        </ul>
        <button onClick={refetch}>다시 불러오기</button>
        </>
    );
}

export default Users;


정상적으로 작동한다.

현재는 컴포넌트가 처음 렌더링 되는 시점부터 API를 요청하고 있는데, 만약 특정 버튼을 눌렀을 때만 API를 요청하고 싶다면, 어떻게 해야하는지 알아보자. POST,DELETE,PUT,PATCH등의 HTTP메서드를 사용하게 되면 필요한 시점세만 API를 호출해야하기 때문에, 필요할 때에만 요청할 수 있는 기능이 필요하다.

useAsync 의 파라미터를 하나 더 추가한다.
function useAsync(callback, deps =[], skip=false)

useEffect에서 skip이 true일 경우에 아무런 작업도 하지 않도록 설정한다.

 useEffect(() => {
        if(skip) return;
        fetchData();
        //eslint-disable-next-line
    },deps);

Users 컴포넌트에서 user가 fasle 일때 null로 리턴하게 했었지만, 그 대신 불러오기라는 버튼을 만들고 그 버튼을 클릭하면 refetch, 즉 useAsync를 통해서 getUsers의 데이터를 리턴하고 User의 return DOM을 렌더링 하게 된다.

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>

여기서 출력된 name을 클릭하면 그에 대한 상세 정보가 출력되게 해보자.
User.js라는 컴포넌트를 생성한다. 클릭된 name의 id에 해당하는 username과 email을 출력한다. 미리 만들었떤 useAsync를 이용해서 state값을 가져오고, getUsers함수를 통해 해당 id에 해당하는 데이터를 리턴한다.

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

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

function User({id}) {
    const [state] = useAsync(() => getUser(id),[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.js컴포넌트에서 User컴포넌트를 추가한다. 이때 Users함수에서 id값의 상태를 관리하는 usestate를 만들어서 User 컴포넌트에게 id 값을 전송할 수 있게 해준다.

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

실행해보면

각각의 name 값을 클릭했을 때 하단에 username과 email이 나타난다.

profile
개발자 꿈나무

1개의 댓글

comment-user-thumbnail
2023년 8월 8일

벨로퍼트님 글이네요

답글 달기