본문 바로가기

리액트

React 커스텀 Hook, await와 axios.all 활용법

React에 커스텀 훅으로 만들어서 처리하기

  • 커스텀 훅은 개발자가 스스로 훅을 정의 할수 있고, 이를 이용해 반복되는 로직을 함수로 뽑아내어 재사용이 가능하다.
  • 폼 데이터 관리, API 요청 상태 관리, 비동기 데이터 요청을 여러 곳에서 사용할 때

상태관리 로직의 재활용이 가능, 코드의 가독성 증가, 상태 관리

리액트 공식문서 예시


      <script src="https://unpkg.com/react@18.2.0/umd/react.development.js"></script>
      <script src="https://unpkg.com/react-dom@18.2.0/umd/react-dom.development.js"></script>

      const { useState, useEffect } = React;
      function useCounter() {
        const [count, setCount] = useState(0);
        useEffect(() => {
          const id = setInterval(() => {
            setCount(c => c + 1);
          }, 1000);
          return () => clearInterval(id);
        }, []);
        return count;
      }

      function CounterComponent() {
        const count = useCounter();
        return (
          <div>
            <h1>Current count: {count}</h1>
          </div>
        );
      }

      ReactDOM.render(<CounterComponent />, document.getElementById('root'));
  • 커스텀 훅은 순수해야 한다.(부수효과X) 다른 데이터가 변경이 되거나 영향이 있으면 안된다.
  • 커스텀 훅의 이름은 use뒤에 대문자로 시작되어야한다. 카멜케이스
  • 모든 Hook은 컴포넌트가 재렌더링될 때 마다 재실행
  • 함수는 조건부 함수가 아니어야 한다. return하는 값은 조건부여서는 안된다.(return count;)

내가 만든 커스텀 훅

      import axios from 'axios'
      import { useEffect, useState } from 'react'

      const useMovieApi = (url) => {
        const [data, setData] = useState([])
        const [loading, setLoading] = useState(false)
        const [error, setError] = useState(null)

        useEffect(() => {
          const fetchData = async () => {
            setLoading(true)
            setError(null)
            try {
              const response = await axios.all(url.map((url) => axios.get(url)))
              const combinedData = response.reduce(
                (acc, res) => acc.concat(res.data),
                []
              )
              setData(combinedData)
            } catch (e) {
              setError(e.message || '에러입니다!')
            }
          }
          fetchData()
        }, [url])

        return { data, error, loading }
      }

      export default useMovieApi
  • url을 외부에서 받아서 그에 맞는 요청을 보내고 state에 상태를 저장한다.
  • useEffect로 초기 렌더링 시 실행시키고, url이 바뀐다면 다시 함수를 실행하여 렌더링 해준다.
  • useEffect훅은 동기적이기 떄문에 안에 로직을 긴 시간을 보내게 하는 코드를 작성하고 렌더링 해야한다면 사용자는 계속 로딩을 기달려야한다. 비동기로 처리하기
  • 비동기로 처리하면은 사용자가 기다리는 동안, 다른 화면은 먼저 렌더링하고, 로딩이라는 것을 인지시켜 줄수 있다.
  • 비동기로 처리하기 떄문에 콜스택을 떠나 웹 API로 가고 이벤트루프가 이를 관찰하면서 실행이 완료되면은 콜백큐에 넣어서 스택에 아무것도 없다면 밀어넣어서 업데이트 해주는 방식이다.

axios.all

       const response = await axios.all(url.map((url) => axios.get(url)))
              const combinedData = response.reduce(
                (acc, res) => acc.concat(res.data),
                []
              )
              setData(combinedData)
  • request를 여러번 보낼수 있다.
  • 받아온 response는 soread요청으로 받아와야 한다.
  • JavaScript 배열과 같은 프로미스(iterable)를 인자로 받고, 각 프로미스의 결과를 담은 배열을 반환
  • axios.all 함수는 여러 개의 프로미스를 병렬로 실행하고, 모든 프로미스의 결과가 반환될 때까지 기다린 뒤 그 결과를 하나의 배열로 반환하는 함수
  • acc는 누산기, 초기값은 지금([])이고, res는 배열의 각 요소, res.data로 data가 있다면 acc([])배열에 추가하고 작업이 다 끝나면 concat으로 새로운 배열로 합친다.

커스텀훅 데이터 받기

 //   URL 데이터 두 개 넘겨주기
  const { data, loading, error } = useMovieApi ({
    url: [MOVIE_URL, MOVIE2_URL],
  })

  //   구조 분해 할당으로 쓸꺼 가져오기
  const [data1, data2] = data

오류

  • 이 코드로 만들었을 떄 data를 못찾는 다는 오류가 계속 나서 정말 짜증났다.
  • state에 처음 값이 [] 빈 배열이기 떄문에 비동기적 처리 때문에 받아서 쓰는 곳에서 data가 계속 없다고 뜬 것이다.
    import axios from 'axios'
    import { useEffect, useState } from 'react'

    const useMovieApi = (url) => {
      const [data, setData] = useState([])
      const [loading, setLoading] = useState(false)
      const [error, setError] = useState(null)

      useEffect(() => {
        const fetchData = async () => {
          setLoading(true)
          setError(null)
          try {
            const response = await axios.all(url.map((url) => axios.get(url)))
            const combinedData = response.reduce(
              (acc, res) => acc.concat(res.data),
              []
            )
            setData(combinedData)
          } catch (e) {
            setError(e.message || '에러입니다!')
          }
        }
        fetchData()
      }, [url])

      return { data, error, loading }
    }

    export default UseMovieApi
  • 이를 해결하기 위해서 useEffect를 여기서 뺴고 받아 쓰는 곳에서 처리를 해주었고
  • axios.all로 받아오는 데이터들에 대한 처리를 각각의 state로 관리하였다. 구조분해할당 사용
  • 커스텀 훅을 사용하면서 안 사실을 렌더링 순서가 보장되어야한다. 즉 return에서 명시해주는 것에 받아쓰는 곳에서 순서를 잘 지켜야 한다는 것이다.

# 오류 2

  const combinedData = response.reduce(
                (acc, res) => acc.concat(res.data),
                []
              )

              axios.all([getUserAccount(), getUserPermissions()])
  .then(axios.spread(function (acct, perms) {
    // Both requests are now complete
  }));
  • reduce와, concat으로 누적해서 새로운 배열로 만들수는 있지만 받아 쓰는 곳에서 구조분해 할당을 하면은 언디파인드 에러가 뜸
  • concat 결과를 acc에 다시 할당하지 않으면, 원본 배열은 변경되지 않아서 데이터가 누적(X)
  • 공식문서에서도

# 오류 3 커스텀훅 데이터 받기 1번 처럼 사용하면 에러가 나온다!

매번 하면서 느끼는 거지만 useMoemo, useCallBack을 활용해 최적화를 잘하는 방법은 어려운거 같다... 더 좋은 방법 있으면 알려주세요


const Main = () => {
  const { data1, data2, loading, error, fetchMovies } = useMovieApi()

  const mainImg = useMemo(() => {
    return data1 && data1.length > 0
      ? `${TMDB_MAIN_IMG}${data1[0].backdrop_path}`
      : ''
  }, [data1])

  useEffect(() => {
    fetchMovies({ urls: [MOVIE_URL, MOVIE2_URL] })
  }, [])

  if (loading) return Loading...
  if (error) return Error: {error}

  if (data1 && data1.length > 0) {
    return (

    )
  }

  return No data available
}

export default Main

# 해결 방법


import axios from 'axios'
import { useState } from 'react'

const useMovieApi = () => {
  const [data1, setData1] = useState([])
  const [data2, setData2] = useState([])
  const [loading, setLoading] = useState(false)
  const [error, setError] = useState(null)

  async function fetchMovies({ urls }) {
    setLoading(true)
    setError(null)
    try {
      const [response1, response2] = await axios.all(
        urls.map((url) => axios.get(url))
      )

      setData1(response1.data)
      setData2(response2.data)
    } catch (e) {
      setError(e.message || 'Error')
      console.error(e)
    } finally {
      setLoading(false)
    }
  }

  return { data1, data2, error, loading, fetchMovies }
}

export default useMovieApi

지금까지 만든 화면

틀린점이 있다면 댓글로 알려주세요! all을 더 좋게 처리하는 방법도 알려주시면 감사하겠습니다!

'리액트' 카테고리의 다른 글

React  (0) 2025.01.07
React-player 라이브러리  (0) 2024.12.31
React Potal Modal 구현하기  (3) 2024.12.31
React scroll시 css 변경하기  (0) 2024.12.31
React 캐로셀  (1) 2024.12.31