React)custom-hook으로 httpRequest 구현하기

미누도 개발한다·2021년 6월 6일
1

fetch를 사용하는 컴포넌트가 많다면, 똑같은 http-request 코드를 컴포넌트 마다 복붙하기보다,
커스텀 훅으로 구현한뒤 다른 파일로 저장하는 것이 간결하다.
더 나아가 useReducer를 사용하여 구현하는게 더 간결하고 좋다.

Can't perform a React state update on an unmounted component 에러해결

기존에는 useState를 활용해서 훅을 짰었는데,
부모 컴포넌트,자식컴포넌트의 각 useEffect에서 fetch작업을 하는 경우에 에러가 발생했었다.

log: Can't perform a React state update on an unmounted component
컴포넌트 업데이트 과정에서 언마운트 된 경우에 생기는 에러인데 아무리 코드를 봐도, 업데이트 중간에 언마운트된 컴포넌트가 없었다... 해결책을 찾다가 디버깅이 쉽도록 useReducer로 구현해봤는데 에러가 해결됐다.

구현코드

JSON placeholder 를 이용해서 fakeData를 fetch 해본다.

hooks/use-http.js

import { useReducer, useCallback } from 'react';

function httpReducer(state, action) {
  if (action.type === 'SEND') {
    return {
      data: null,
      error: null,
      status: 'pending',
    }; 
  }
  if (action.type === 'SUCCESS') {
    return {
      data: action.payload,
      error: null,
      status: 'completed',
    };
  }

  if (action.type === 'ERROR') {
    return {
      data: null,
      error: action.errorMessage,
      status: 'completed',
    };
  }
  return state;
}

function useHttp(requestFunction, startWithPending = false) {
  const [httpState, dispatch] = useReducer(httpReducer, {
    status: startWithPending ? 'pending' : null,
    data: null,
    error: null,
  });

  const sendRequest = useCallback(
    async function (requestData) {
      dispatch({ type: 'SEND' });
      try {
        const responseData = await requestFunction(requestData);
        console.log(responseData);
        dispatch({ type: 'SUCCESS', payload:responseData });
      } catch (error) {
        dispatch({
          type: 'ERROR',
          errorMessage: error.message || 'Something went wrong!',
        });
      }
    },
    [requestFunction]
  );

  return {
    sendRequest,
    ...httpState,
  };
}

export default useHttp;

startWithPending변수는 req요청 전, 초기화면 렌더링 에서 바로 로딩스피너를 띄울지 말지 결정한다.


lib/api.js (실제 비동기처리코드)
const reqURL = 'https://jsonplaceholder.typicode.com/todos/';

export async function getSingleTodo(id) {
    
    const response = await fetch(`${reqURL}${id}`);
    const data = await response.json();

    if(!response.ok){ 
        throw new Error();
    }

    return data;
}

export async function getUsers(){
    const response = await fetch('http://jsonplaceholder.typicode.com/users');
    if(!response.ok){
        throw new Error();     
    }
    const data = await response.json();
    return data;
}

App.js

import React,{useEffect} from 'react';
import Complement from './components/Complement';
import Quote from './components/Quote';
import {getSingleTodo} from './lib/api';
import useHttp from './hooks/use-http';
import Person from './components/Person';
function App() {
  const {status,data,error,sendRequest} = useHttp(getSingleTodo,false);

  useEffect(()=>{
    sendRequest(1); 
  },[sendRequest]); 

  let content = <div>패치 전..</div>
  if(status ==='pending'){
    content = <div>Loading....</div>
  }
  if(status ==='completed'){
    content = <div>{data.title}</div>
  }
  if(error){
    content = <div>{error}</div>
  }
  return (
    <div>
      {content}
      <Person/>
    </div>
  );
}

export default App;

Person.js
import React,{useEffect} from 'react'
import useHttp from '../hooks/use-http';
import {getPerson} from '../lib/api';

function Person() {
    const {sendRequest,status,data,error} = useHttp(getPerson,true);

    useEffect(()=>{
        sendRequest();    
    },[]);
    
    let content = <div>패치 전...</div>
    if(status ==='pending'){
        content = <div>loading.....</div>
    }
    if(status==='completed' && data){
        content = 
        <ul>
            {data.map((item)=><li key ={item.id}>item.name</li>)}
        </ul>
    }

    return (
        <div>
            {content}        
        </div>
    )
}

export default Person

예시를 위해서 정말 간단히 만들었는데,
App 컴포넌트에서는, 할 일을 fetch를 하고 자식컴포넌트인 Person 에서는 User들을 fetch를 한다.

useHttp(커스텀 훅) 코드 안에서 생성된 sendRequest 함수는 클로저를 통해 dispatch 함수에 접근할수 있다.
App 컴포넌트에서, 훅에 실제 비동기처리 코드함수를 매개변수로 전달하면, 훅에서 생성된 함수인 sendRequest는 클로저를 통해 해당 비동기처리 코드함수(requestFunction)에 접근할 수 있게된다. App 컴포넌트에서 sendReqeust 함수를 리턴받은다음, 직접 실행시켜주는 구조이다.

profile
빨리 가는 유일한 방법은 제대로 가는 것이다

0개의 댓글