useFetch 커스텀 훅을 만들다가 삽질한 내용을 기록
함수형 컴포넌트에서 로직을 쉽게 재사용하기 위해 만드는 hook이다.
재사용 되는 로직들을 커스텀 훅으로 만들어 사용한다.
사람들이 자주 만들어 쓰는 커스텀 훅들은 다음과 같다.
아래 1~14번까지는 npm으로 설치해 사용 가능하다고 한다. (링크)
15 ~ 는 링크 보고 새로 만들면 된다. 외국 유튜버 ->(링크)
Bold 처리된것은 내가 유용하다고 생각했던 커스텀 훅
useTitle: react document의 title을 몇개의 hooks와 함께 바꾸는 것
useInput: 그냥 input 역할
usePageLeave: 유저가 page를 벗어나는 시점을 발견하고 함수를 실행함
useClick: 누군가 element 클릭하는 시점을 발견해
useFadeIn: 어떤 element든 상관없이 애니메이션을 element 안으로 서서히 사라지게 만들어
useFullscreen: 어떤 element든 풀스크린으로 만들거나 일반화면으로 돌아갈 수 있도록
useHover: 어떤 것에 마우스를 올렸을 때 감지
useNetwork: online, offline 확인
useNotification: notification API 사용할 때 유저에게 알림을 보내줘
useScroll: 스크롤을 사용할 때를 감지해 알려줘
useTabs: 웹사이트에 메뉴 또는 무엇이든간에 tab을 사용하기 매우 쉽게 만들어줘
usePreventLeave: 유저가 변경사항이나 무엇이든간에 저장하지 않고 페이지를 벗어나길 원할 때 확인하는 것
useConfirm: 위와 비슷한데, 어떤 기능이 존재하고
useAxios,useFetch: HTTP requests client axios을 위한 wrapper 같은 것
useLocalStorage : 로컬스토리지 쉽게 사용
useEventListener
useMediaQuery
useDarkMode : 다크 모드 관련
비동기 http통신하는 useFetch hook (axios 사용)을 만들다가 알게된 내용들이다.
코드와 함께 설명
후에 밑에 설명할 때 편의상 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 하고자 하는 항목의 수가 많거나 같은 컴포넌트에서 한번만 사용 할때
후에 밑에 순서 설명할때 편의를 위해 A라고 칭하겠다.
이 부분에서 꽤 헤맸는데 헤맸던 포인트는 3가지가 있다.
- 커스텀훅을 변수에 담고 바로 JSX에 적용 (랜더링)
- 실행 순서
- 한 컴포넌트 안에서 동일한 커스텀 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>
)
}
위 코드는 대략적인 구조만 작성한 코드임
다른 예제들을 보면 A에서 useEffect를 거치지 않고 바로 화면에 랜더링 하는데, 그러면 데이터가 바뀔때 유저입장에서 바로 바뀌지 않아 , 바로 바꿔주기 위해 A에서 state를 사용하고, setState를 해주기 위해 A에서 useEffect를 사용한다.
그런데 내가 보여줄 페이지는 유저 프로필 화면이었고 이름,자기소개 변경 시 바로 랜더링이 되야 하기 때문에 state를 사용해야 할 것 같았다.
그래서 커스텀 훅을 통해 값을 받아와 setState 함수를 사용해 state에 넣어주었고 화면이 mount 될 때 하기위해 useEffect 안에서 사용하였다.
A : 사용 컴포넌트
B : 커스텀 훅
useEffect의 실행 순서를 정확하게 알아야 한다!
로그는 위 코드를 참고하면 될 듯하다.
내가 이해가 안됬던 부분들을 정리하자면
3번째줄 - 왜 B에서 useEffect 는 실행됬는데 axios 코드가 먼저 실행이 안되나?
-> 실행이 안되니 5번째 줄 A에서 null값이 들어오고, A 에서 null값예외처리를 안해준 채 화면에 랜더링 시 에러가 난다. (당연히 null값을 랜더링하려 하니까..)
-> 동기/비동기 문제는 아니였다.
밑에서 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) A 에 B의 리턴값이 들어옴
5) A 의 state가 변경되어 재랜더링 되고 이때 알맞은 값이 들어옴
그런데 이때 A기준-> 2) 에서는 null값으로 한번 들어왔다가 바로 5)에서 재 랜더링 되기 때문에
A에서 state를 화면에 출력하거나 console로 확인 할 때 null값(B에서 retrun할 state의 초기 값) 예외처리를 해주지 않으면 에러가 발생한다.
왜냐하면 처음 실행 될 때 2) 에서 null값을 참조하려고 하기 때문
이때 null값은 B에서 선언하는 return 할 state의 초기값이다. (커스텀 훅 useState부분 코드 참고)
결론 : 커스텀 훅에서 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
위 코드는 유저정보를 가져오는 페이지였고, 유저 정보를 팝업창으로 변경할 수 있었다.
그래서 내가 한 방법은 변경하는 팝업창에 setState함수를 props로 전송한 뒤 DB 저장 후 setState 함수를 실행시켜 주었다. 깔끔하게 동작!