본문 바로가기
코딩 정보/React

[React] useEffect와 관련되어 순차적으로 에러 핸들링 해보기

by 꽁이꽁설꽁돌 2024. 8. 10.
728x90
반응형

데이터를 불러오는 과정을 만들고자 한다.

그 과정에서 useEffetc를 통해 에러핸들링을 순차적으로 만들어보면서 에러가 나는 부분을 해결하고자 한다.

 

이 글을 보기 전에 커스텀 훅의 개념과 useEffect의 실행시점, useCallback에 대해 알고 가면 좋다.

아래를 참고하자

https://be-senior-developer.tistory.com/157

 

[React] Custom Hook에 대해 알아보자

목차 Custom Hook의 정의리액트 훅의 첫번째 규칙은 리액트 훅은 리액트 컴포넌트안에서만 사용해야 한다.이 첫번째 규칙을 더 유연하게 만들어 주는 것이 Custom Hook으로 컴포넌트에들어가 있는 함

be-senior-developer.tistory.com

https://be-senior-developer.tistory.com/147

 

[React] sideEffect가 무엇이고 useEffect에 대해 알아보자

목차 Side Effect란?코드가 의도한 주된 효과 외에 추가적으로 발생하는 부수 효과를 말한다.리액트에서는 현재의 컴포넌트에 직접적인 영향을 미치지 않는 작업이라고 한다.  현 컴포넌트 랜더

be-senior-developer.tistory.com

 

 

먼저 일반적인 get요청을 함수로 만들어 준다.

sendHttpRequest.jsx

async function sendHttpRequest(url, config) {
  const response = await fetch(url, config);
  const resData = await response.json();
  //에러 응답인 경우
  if (!response.ok) {
    throw new Error(
      resData.message || "Something went wrong, failed to send request."
    );
  }
  return resData;
}

 

 

이런식으로 loading, error, data를 만들어주고 반환한다.

useHttp.jsx

export default function useHttp(url, config) {
  const [data, setData] = useState();
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState();
    async function sendRequest() {
      setIsLoading(true);
      try {
        const resData = sendHttpRequest(url, config);
        setData(resData);
      } catch (error) {
        setError(error.message || "Something went wrong!");
      }
      setIsLoading(false);
    }
 	console.log(data);
  return { data, isLoading, error };
}

 

Selection.jsx

import MealItem from "./MealItem";
import useHttp from "./hooks/useHttp";

function Selection() {

  const {
    data: meals,
    isLoading,
    error,
  } = useHttp("http://localhost:4000/meals");
  
  return (
    <ul id="meals">
      {isLoading && <p>loading...</p>}
      {!isLoading &&
        meals.map((meal) => <MealItem meal={meal} key={meal.id}></MealItem>)}
    </ul>
  );
}

export default Selection;

 

console에 undefined가 찍힌 것을 보아 아예 data가 받아지지 않아 map이 작동하지않는다는 것을 알 수 있다.

 

따라서 useEffect를 통해 함수를 실행하도록 바꾸어 코드를 다음과 같이 수정해 주었다.

 

useHttp.jsx

export default function useHttp(url, config) {
  const [data, setData] = useState();
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState();
  async function sendRequest() {
    setIsLoading(true);
    try {
      const resData = sendHttpRequest(url, config);
      setData(resData);
    } catch (error) {
      setError(error.message || "Something went wrong!");
    }
    setIsLoading(false);
  }
  useEffect(() => {
    if (config || config.method === "GET") {
      sendRequest();
    }
  }, [sendRequest, config]);
 
  return { data, isLoading, error };
}

 

 

여전히 map의 배열이 undefined라는 것을 보아 data가 안 받아진 것을 볼 수 있다.

그래서 분석을 해보면 useEffect는 컴포넌트가 랜더링된 후 시행되기 때문에 data의 초기값이 정의가 되있지 않은 것이다.

따라서 다음과 같이 초기값을 넣는 코드로 바꾸어 주었다.

 

useHttp.jsx

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

async function sendHttpRequest(url, config) {
  const response = await fetch(url, config);
  const resData = await response.json();
  //에러 응답인 경우
  if (!response.ok) {
    throw new Error(
      resData.message || "Something went wrong, failed to send request."
    );
  }
  return resData;
}

export default function useHttp(url, config, initialValue) {
  const [data, setData] = useState(initialValue);
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState();
  async function sendRequest() {
    setIsLoading(true);
    try {
      const resData = sendHttpRequest(url, config);
  
      setData(resData);
    } catch (error) {
      setError(error.message || "Something went wrong!");
    }
    setIsLoading(false);
    
  }
  useEffect(() => {
    if (config || config.method === "GET") {
      sendRequest();
      console.log(data);
    }
  }, [sendRequest, config]);
 
  return { data, isLoading, error };
}

 

콘솔에 배열이 찍힌 것을 보아 데이터는 잘 가져온 것을 볼 수 있다. 그런데 meals.map이 not a function이라는 것을 보아 배열 형태가 잘못된 것 같다. 그래서 다음과 같이 코드를 바꾸어 콘솔을 찍어 보았다.

 

useHttp.jsx

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

async function sendHttpRequest(url, config) {
  const response = await fetch(url, config);
  const resData = await response.json();
  //에러 응답인 경우
  if (!response.ok) {
    throw new Error(
      resData.message || "Something went wrong, failed to send request."
    );
  }
  return resData;
}

export default function useHttp(url, config, initialValue) {
  const [data, setData] = useState(initialValue);
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState();
  async function sendRequest() {
    setIsLoading(true);
    try {
      const resData = sendHttpRequest(url, config);
        console.log(resData);
      setData(resData);
    } catch (error) {
      setError(error.message || "Something went wrong!");
    }
    setIsLoading(false);
    
  }
  useEffect(() => {
    if (config || config.method === "GET") {
      sendRequest();
      console.log(data);
    }
  }, [sendRequest, config]);
 
  return { data, isLoading, error };
}

 

Promise객체가 반환된 것이 문제였다. 그래서 await를 통해 배열로 반환받게 수정하였다.

 

해결했더니 무한루프의 문제가 발생하였다... 무엇이 문제일까?

바로 useEffect의 의존성 배열이었다. 함수는 객체로서 새로 만들어지기 때문에 useCallback을 통해 재생성을 막아주어야 한다.

 

useHttp.jsx

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

async function sendHttpRequest(url, config) {
  const response = await fetch(url, config);
  const resData = await response.json();
  //에러 응답인 경우
  if (!response.ok) {
    throw new Error(
      resData.message || "Something went wrong, failed to send request."
    );
  }
  return resData;
}

export default function useHttp(url, config, initialValue) {
  const [data, setData] = useState(initialValue);
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState();
  
  //useCallback넣어서 재생성 막자!
  const sendRequest = useCallback(async function sendRequest() {
    setIsLoading(true);
    try {
    //await를 통해 해결!
      const resData = await sendHttpRequest(url, config);

      setData(resData);
    } catch (error) {
      setError(error.message || "Something went wrong!");
    }
    setIsLoading(false);
    
  },[url, config])
  useEffect(() => {
    if (config || config.method === "GET") {
      sendRequest();
      console.log(data);
    }
  }, [sendRequest, config]);
 
  return { data, isLoading, error };
}

 

그래도 또 오류가 난다... (포기할까? 여기까지 했는데 좀만 더 참자...) 함수 객체의 재생성은 막았으니 분명 문제가 없다.

그렇다면 분명 config 의존성 배열의 문제이다. 

 

그렇다! config객체가 계속해서 재랜더링을 통해 재생성되고 있었다! 그래서 나는 함수바깥으로 객체를 빼줌으로써 해결하였다.

 

Selection.jsx

import MealItem from "./MealItem";
import useHttp from "./hooks/useHttp";
const requestConfig = {};
///따라서 바깥에 만들어 주어 컴포넌트의 함수의 랜더링으로 인한 객체 재생성을 막아준다.
function Selection() {
  //초기 객체는 컴포넌트 함수에서 생성됨으로 의존성 배열의 변화가 생기게 됨
  const {
    data: meals,
    isLoading,
    error,
  } = useHttp("http://localhost:4000/meals",{}, []);
  return (
    <ul id="meals">
      {isLoading && <p>loading...</p>}
      {!isLoading &&
        meals.map((meal) => <MealItem meal={meal} key={meal.id}></MealItem>)}
    </ul>
  );
}

export default Selection;

 

데이터가 드디어 불러와졌다 ㅠㅠㅠㅠ

나는 여기서 더 디테일을 추가했다.

 

config가 get요청이거나 config가 아예 없을 경우에 sendRequest함수를 실행하게 만들었다.

 

useHttp.jsx

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

async function sendHttpRequest(url, config) {
  const response = await fetch(url, config);
  const resData = await response.json();
  //에러 응답인 경우
  if (!response.ok) {
    throw new Error(
      resData.message || "Something went wrong, failed to send request."
    );
  }
  return resData;
}

export default function useHttp(url, config, initialValue) {
  const [data, setData] = useState(initialValue);
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState();
  const sendRequest = useCallback(
    async function sendRequest(data) {
      setIsLoading(true);
      try {
        const resData = await sendHttpRequest(url, { ...config, body: data });
        console.log(resData);
        setData(resData);
      } catch (error) {
        setError(error.message || "Something went wrong!");
      }
      setIsLoading(false);
    },
    [url, config]
  );
  useEffect(() => {
    if ((config && (config.method === "GET" || !config.method)) || !config) {
      sendRequest();
    }
  }, [sendRequest, config]);

  return { data, isLoading, error, sendRequest };
}

 

error가 날때 메세지와 그 이유가 표시되게끔 더 수정해보았다.

 

Selection.jsx

import MealItem from "./MealItem";
import useHttp from "./hooks/useHttp";
import Errors from "./Error";
const requestConfig = {};
///따라서 바깥에 만들어 주어 컴포넌트의 함수의 랜더링으로 인한 객체 재생성을 막아준다.
function Selection() {
  //초기 객체는 컴포넌트 함수에서 생성됨으로 의존성 배열의 변화가 생기게 됨
  const {
    data: meals,
    isLoading,
    error,
  } = useHttp("http://localhost:4000/meals", requestConfig, []);
  if (isLoading) {
    return <p className="center">loading...</p>;
  }
  if (error) {
    return <Errors title={"Failed to fetch meals"} message={error}></Errors>
  }
  return (
    <ul id="meals">
      {meals.map((meal) => (
        <MealItem meal={meal} key={meal.id}></MealItem>
      ))}
    </ul>
  );
}

export default Selection;

 

반응형