Custom Hook

고은찬·2021년 10월 5일
1

React.js

목록 보기
4/4
post-thumbnail

useFetch 커스텀 훅을 만들다가 삽질한 내용을 기록

▶커스텀 훅이란?

소개

함수형 컴포넌트에서 로직을 쉽게 재사용하기 위해 만드는 hook이다.

언제 사용하지?

재사용 되는 로직들을 커스텀 훅으로 만들어 사용한다.

사람들이 자주 만들어 쓰는 커스텀 훅들은 다음과 같다.
아래 1~14번까지는 npm으로 설치해 사용 가능하다고 한다. (링크)
15 ~ 는 링크 보고 새로 만들면 된다. 외국 유튜버 ->(링크)

Bold 처리된것은 내가 유용하다고 생각했던 커스텀 훅
  1. useTitle: react document의 title을 몇개의 hooks와 함께 바꾸는 것

  2. useInput: 그냥 input 역할

  3. usePageLeave: 유저가 page를 벗어나는 시점을 발견하고 함수를 실행함

  4. useClick: 누군가 element 클릭하는 시점을 발견해

  5. useFadeIn: 어떤 element든 상관없이 애니메이션을 element 안으로 서서히 사라지게 만들어

  6. useFullscreen: 어떤 element든 풀스크린으로 만들거나 일반화면으로 돌아갈 수 있도록

  7. useHover: 어떤 것에 마우스를 올렸을 때 감지

  8. useNetwork: online, offline 확인

  9. useNotification: notification API 사용할 때 유저에게 알림을 보내줘

  10. useScroll: 스크롤을 사용할 때를 감지해 알려줘

  11. useTabs: 웹사이트에 메뉴 또는 무엇이든간에 tab을 사용하기 매우 쉽게 만들어줘

  12. usePreventLeave: 유저가 변경사항이나 무엇이든간에 저장하지 않고 페이지를 벗어나길 원할 때 확인하는 것

  13. useConfirm: 위와 비슷한데, 어떤 기능이 존재하고

  14. useAxios,useFetch: HTTP requests client axios을 위한 wrapper 같은 것


  1. useLocalStorage : 로컬스토리지 쉽게 사용

  2. useEventListener

  3. useMediaQuery

  4. useDarkMode : 다크 모드 관련

▶ 삽질 후 알게 된 것

비동기 http통신하는 useFetch hook (axios 사용)을 만들다가 알게된 내용들이다.
코드와 함께 설명

useFetch.js (커스텀 훅) : B

후에 밑에 설명할 때 편의상 B 라고 칭하겠다.

구조를 보면 useEffect가 실행되는데, 이때 의존성 배열을 넣지 않으면 무한랜더링된다.
(머리로는 알겠는데 설명이 잘 안된다. 왜 무한랜더링되는지 이유 보강하기)


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


/**
 * @Axios 커스텀 hooks
 * **/
function UseFetch(fetch_url="", options = null, params=null) {

    const [value,set_values] = useState(null);
    const [error,set_error] = useState(null);
    const [loading,set_loading] = useState(false);
    const [url,set_url] = useState(fetch_url);

    useEffect( async ()=> {
        let isMounted = true;
        set_loading(true);

          await axios.get(url)
            .then(res => {
                console.log("useFetch response ↓")
                console.log(res);
                const data = res.data;

                if (isMounted){
                    set_values(data);
                    set_error(null);

                    console.log("useFetch value ↓")
                    console.log(value);
                }

                })
            .catch(err=>{
                console.log(err);
                set_error(error);
                set_values(null);
            })
            .finally(() => {
                console.log("finally");
                isMounted && set_loading(false);
            });
    },[url]) //useEffect 랜더링 순서로 인해 이곳에 의존성 배열을 넣지 않으면 setstate로 인해 무한랜더링됨


    // 이곳에서 배열형태로 반환해야 사용하는 컴포넌트에서 여러번 호출 가능하다.
    // {value,error,loading} 을 사용해 Object로 return하면 사용 컴포넌트에서 사용할 때
    // const {value,error,loading} = useFetch(url) 형태로만 사용가능하고 , 값을 바꿔
    // const {v,e,l} = useFetch(url) 로 하면 인식하지 못한다.
    return [value,error,loading];
}

export default UseFetch;

Tips 💢
커스텀 Hook 에서 값을 리턴할 때 array or object 로 해준다. ( 코드 return 부분 참고)
1) array : return 하고자 하는 항목의 수가 적거나 같은 컴포넌트에서 여러번 사용 할때
2) object : return 하고자 하는 항목의 수가 많거나 같은 컴포넌트에서 한번만 사용 할때


사용 컴포넌트.js : A

후에 밑에 순서 설명할때 편의를 위해 A라고 칭하겠다.
이 부분에서 꽤 헤맸는데 헤맸던 포인트는 3가지가 있다.

  1. 커스텀훅을 변수에 담고 바로 JSX에 적용 (랜더링)
  2. 실행 순서
  3. 한 컴포넌트 안에서 동일한 커스텀 hook 여러번 사용하는 방법

먼저 코드를 보자.

  • 다른 예제들 사용 방식
//다른 예제 사용 방식 (공홈 포함) -> 커스텀 훅 가져다 쓰는 컴포넌트
import React from 'react';

function A컴포넌트() {
  
  const {value,error,loading} = useCustomhook();

  -----jsx-----
    return(
      <div>{value}</div> or
      <div>{JSON.stingfy(value)}</div>
    )
}
  • 내가 사용한 방식
//내가 사용한 방식 -> 커스텀 훅 가져다 쓰는 컴포넌트
import React,{useState, useEffect} from 'react';

function A컴포넌트() {
  
  // 이 컴포넌트에서 useCustomhook()을 여러번 호출할 거라 
  // 커스텀 훅 내부에서 배열 형태로 return => (커스텀 훅 안) return [value,error,loading]
  const [value1,error1,loading1] = useFetch('요청할 url 주소');
  const [value2,error2,loading2] = useFetch('요청할 url 주소');
  const [value3,error3,loading3] = useFetch('요청할 url 주소');
  const [state,setState] = useState('');
  
  useEffect(()=>{
     console.log('A컴포넌트의 useEffect 실행');
     console.log(value1,value2,value3);
    
    // 여기서 null 부분은 커스텀 훅에서 return하는 value state의 초기값이여야 한다.
  	if(value != null) {
      
      	   // 유저 정보들이 담겨있는 JSON을 리턴하기 떄문에 여기서 최종 값으로 대입해줘야함
           setState(value1.data.최종값)
        }
    
  },[value1])

  -----jsx-----
    return(
      <div>{state}</div>
    )
}

삽질 1. 다른 예제들의 사용법 - return값 바로 적용

위 코드는 대략적인 구조만 작성한 코드임

다른 예제들을 보면 A에서 useEffect를 거치지 않고 바로 화면에 랜더링 하는데, 그러면 데이터가 바뀔때 유저입장에서 바로 바뀌지 않아 , 바로 바꿔주기 위해 A에서 state를 사용하고, setState를 해주기 위해 A에서 useEffect를 사용한다.

그런데 내가 보여줄 페이지는 유저 프로필 화면이었고 이름,자기소개 변경 시 바로 랜더링이 되야 하기 때문에 state를 사용해야 할 것 같았다.

그래서 커스텀 훅을 통해 값을 받아와 setState 함수를 사용해 state에 넣어주었고 화면이 mount 될 때 하기위해 useEffect 안에서 사용하였다.

삽질 2. 실행순서 🙅‍♂️

A : 사용 컴포넌트
B : 커스텀 훅
useEffect의 실행 순서를 정확하게 알아야 한다!


로그는 위 코드를 참고하면 될 듯하다.

내가 이해가 안됬던 부분들을 정리하자면

  1. 3번째줄 - 왜 B에서 useEffect 는 실행됬는데 axios 코드가 먼저 실행이 안되나?
    -> 실행이 안되니 5번째 줄 A에서 null값이 들어오고, A 에서 null값예외처리를 안해준 채 화면에 랜더링 시 에러가 난다. (당연히 null값을 랜더링하려 하니까..)
    -> 동기/비동기 문제는 아니였다.

  2. 밑에서 2번째 줄 null - 왜 B 에서 axios 를 통해 response를 받아오는 건 되는데(밑에서 4번째줄)
    그 response를 setState 하는건 null값이라고 뜰까?
    null값이라 뜨는데 어떻게 A에서는 받아와질까?
    -> 이 부분은 아직도 잘 모르겠다.. 아시는분 댓글부탁


    요약하면 아래 순서다.

      1) A 상단에서 커스텀 훅 실행 
       2) A 에서 useEffect 실행 (mount) -> 이때 값들을 console이나 state로 화면에 출력하면 초기값(null)값으로 나온다.
       3) B 에서 useEffect 실행 (mount) -> 이때 fetch 하여 값을 받아옴 
       4) AB의 리턴값이 들어옴
       5) A 의 state가 변경되어 재랜더링 되고 이때 알맞은 값이 들어옴
       

    그런데 이때 A기준-> 2) 에서는 null값으로 한번 들어왔다가 바로 5)에서 재 랜더링 되기 때문에
    A에서 state를 화면에 출력하거나 console로 확인 할 때 null값(B에서 retrun할 state의 초기 값) 예외처리를 해주지 않으면 에러가 발생한다.

    왜냐하면 처음 실행 될 때 2) 에서 null값을 참조하려고 하기 때문

    이때 null값은 B에서 선언하는 return 할 state의 초기값이다. (커스텀 훅 useState부분 코드 참고)


삽질 3. 한 컴포넌트 안에서 같은 커스텀 훅 여러번 사용하기

결론 : 커스텀 훅에서 return 시 배열형태로 반환하기 []
사용하는 컴포넌트에서도 배열 형태로 값을 받아야 한다.

1) 처음에는 커스텀 훅에서 state를 return 할때 Object 형태로 return했었다.

//Object 형태 
return {value,error,loading}

근데 이렇게 하니, A (사용컴포넌트) 에서 사용하려니

const {value,error,loading} = useFetch("요청할url1"); 
const {value1,error1,loading1} = useFetch("요청할url2");

console.log(value) //인식 O 
console.log(value1) //인식 X 

위와 같은 문제가 있었고 이러면 한 컴포넌트에서 같은 커스텀 훅을 여러번 사용할 수 없었다.
그래서 찾아본 결과 좋은 글을 보았다. 한번 읽어보는 것을 권한다.

결론적으로는 배열형태로 반환해 해결하였다!

//useFetch.js return 부분 -> 배열형태로 
return [value,error,loading]



// A컴포넌트: 사용 컴포넌트 커스텀 훅 호출 부분 
const [value,error,loading] = useFetch("요청할url1"); 
const [value1,error1,loading1] = useFetch("요청할url2");

console.log(value) //인식 O 
console.log(value1) //인식 O




받아온 데이터를 변경한 뒤 실시간 적용하려면?(ex.닉네임변경 등)

위 코드는 유저정보를 가져오는 페이지였고, 유저 정보를 팝업창으로 변경할 수 있었다.

  1. 커스텀 훅으로 유저데이터를 받아온 뒤 A의 useEffect 안에서 setstate로 화면에 데이터를 배치 후
  2. 유저데이터 수정 팝업에서 DB값을 수정하기만 하면 바로 변하지 않는다.
  3. 변경내역을 재랜더링 해주어야 한다.
    1) 팝업창에 해당 변경값을 바꾸는 setState함수를 props로 보내 DB 저장 후 setState함수로 변경
    2) DB 저장 후 유저 데이터 화면 재랜더링 - 커스텀 훅으로 유저데이터 받아오는 부분 재랜더링하기

그래서 내가 한 방법은 변경하는 팝업창에 setState함수를 props로 전송한 뒤 DB 저장 후 setState 함수를 실행시켜 주었다. 깔끔하게 동작!

profile
연애하는 개발자

0개의 댓글