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

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

by 꽁이꽁설꽁돌 2024. 7. 31.
728x90
반응형

 

목차

     

    Custom Hook의 정의

    리액트 훅의 첫번째 규칙은 리액트 훅은 리액트 컴포넌트안에서만 사용해야 한다.

    이 첫번째 규칙을 더 유연하게 만들어 주는 것이 Custom Hook으로 컴포넌트에

    들어가 있는 함수를 감싸고 재사용하게 해준다.

     

    즉 반복되는 로직을 하나로 묶어 재사용하기 위한 자신이 만든 Hook을 말한다.

     

    아래와 같이 구조가 반복되는 코드예

     useEffect(() => {
        async function fetchUser() {
          setIsLoading(true);
          try {
            const places = await fetchUserPlaces();
            setUserPlaces(places);
            setIsLoading(false);
          } catch (error) {
            setError({ error: error.message || "Failed to fetch user places." });
          }
          setIsLoading(false);
        }
        fetchUser();
      }, []);

     

     useEffect(() => {
        async function fetchData() {
          setIsLoading(true);
          try {
            //await 해야함
            const places = await fetchAvailablePlaces();
            //프로미스를 반환하지 않음
            navigator.geolocation.getCurrentPosition((position) => {
              const sortedPlaces = sortPlacesByDistance(
                places,
                position.coords.latitude,
                position.coords.longitude
              );
            
              setAvailableplaces(sortedPlaces);
              setIsLoading(false);
            });
            //setAvailableplaces(places);
            //fetch도 실패한 경우 네트워크 에러
          } catch (error) {
            
            setError({ message: error.message || "cant fetch" });
            setIsLoading(false);
          }
        }
        fetchData();
      }, []);

     

     

    Custom useFetch Hook 

    각각 독립적인 상태의 스냅샷을 받기 때문에 한 컴포넌트의 상태를 바꾼다고 해서 다른 컴포넌트에 영향을 주지 않는다.

    import { useEffect, useState } from "react";
    
    //커스텀 훅 이름은 use로 시작해야 함 (use로 시작하는 것은 hook으로 인식)
    //최대한 일반적인 사용이 가능하게 만들어줌
    export default function useFetch(fetchFn, initialValue) {
      const [isLoading, setIsLoading] = useState();
      const [error, setError] = useState();
      const [fetchedData, setFetchedData] = useState(initialValue);
      useEffect(() => {
        async function fetchData() {
          setIsLoading(true);
          try {
            const data = await fetchFn();
            setFetchedData(data);
          } catch (error) {
            setError({ error: error.message || "Failed to fetch data" });
          }
          setIsLoading(false);
        }
        fetchData();
      }, [fetchFn]);
      //외부의 값 의존성
      return {
        isLoading,
        fetchedData,
        setFetchedData,
        error,
      };
    }

     

    커스텀 훅 활용 방법

      const { isLoading, error, 
      fetchedData: userPlaces, //별칭 지정
        setFetchedData: setUserPlaces,
      } = useFetch(fetchUserPlaces, []);

     

     

    유동적으로 커스텀 훅 쓰기

    위에서 만든 커스텀 훅을 통해 다음 코드를 바꾸는데

    중간에 정렬하는 과정이 포함되어 있다. 이럴때는 PROMISE 객체를 반환하는 함수를 만들어 해결할 수 있다.

    import Places from "./Places.jsx";
    import { useState, useEffect } from "react";
    import Error from "./Error.jsx";
    import { sortPlacesByDistance } from "../loc.js";
    import { fetchAvailablePlaces } from "../http.js";
    export default function AvailablePlaces({ onSelectPlace }) {
      const [availablePlaces, setAvailableplaces] = useState([]);
      const [isLoading, setIsLoading] = useState(false);
      const [error, setError] = useState();
    
      useEffect(() => {
        async function fetchData() {
          setIsLoading(true);
          try {
            //await 해야함
            const places = await fetchAvailablePlaces();
            //프로미스를 반환하지 않음
            navigator.geolocation.getCurrentPosition((position) => {
              const sortedPlaces = sortPlacesByDistance(
                places,
                position.coords.latitude,
                position.coords.longitude
              );
            
              setAvailableplaces(sortedPlaces);
              setIsLoading(false);
            });
            //setAvailableplaces(places);
            //fetch도 실패한 경우 네트워크 에러
          } catch (error) {
            
            setError({ message: error.message || "cant fetch" });
            setIsLoading(false);
          }
        }
        fetchData();
      }, []);
    
      if (error) {
        return <Error title="An error occured!" message={error.message}></Error>;
      }
    
      return (
        <Places
          title="Available Places"
          places={availablePlaces}
          isLoading={isLoading}
          loadingText={"loading..."}
          fallbackText="No places available."
          onSelectPlace={onSelectPlace}
        />
      );
    }

     

    코드 구현

    import Places from "./Places.jsx";
    
    import Error from "./Error.jsx";
    import { sortPlacesByDistance } from "../loc.js";
    import { fetchAvailablePlaces } from "../http.js";
    import useFetch from "../hooks/useFetch.js";
    
    async function fetchSortedPlaces() {
      const places = await fetchAvailablePlaces();
      //프로미스 기반 함수로 바꾸어줌
      return new Promise((resolve) => {
        navigator.geolocation.getCurrentPosition((position) => {
          const sortedPlaces = sortPlacesByDistance(
            places,
            position.coords.latitude,
            position.coords.longitude
          );
          resolve(sortedPlaces);
        });
      });
    }
    
    export default function AvailablePlaces({ onSelectPlace }) {
      const {
        fetchedData: availablePlaces,
        isLoading,
        error,
      } = useFetch(fetchSortedPlaces, []);
    
      if (error) {
        return <Error title="An error occured!" message={error.message}></Error>;
      }
    
      return (
        <Places
          title="Available Places"
          places={availablePlaces}
          isLoading={isLoading}
          loadingText={"loading..."}
          fallbackText="No places available."
          onSelectPlace={onSelectPlace}
        />
      );
    }
    반응형