매일매일
Published 2023. 3. 27. 21:51
Custom Hook React

useFetch

fetch 할 때 반복되는 코드를 커스텀 훅으로 만들어 활용할 수 있다.

import { useState, useEffect } from 'react';

const useFetch = (url) => {
  const [data, setData] = useState(null);
  const [isPending, setIsPending] = useState(true);
  const [error, setError] = useState(null);

useEffect(() => {
      fetch(url, {
      // get의 경우 헤더를 작성하지 않아도 괜찮
        headers: {
          "Content-Type" : "application/json",
          Accept: "application/json"
        }
      })
      .then(res => {
        if (!res.ok) {
          throw Error('could not fetch the data for that resource');
        } 
        return res.json();
      })
      .then(data => {
        setIsPending(false);
        setData(data);
        setError(null);
      })
      .catch(err => {
        setIsPending(false);
        setError(err.message);
      })
  }, [url])
// url에 영향받으므로 종속배열에 url
// 리턴은 값으로
  return [data, isPending, error];
}

export default useFetch;

커스텀훅으로 만들어 두면 필요할 때 마다 위의 코드를 전체를 다시 반복할 필요 없이 아래와 같이 활용하면 된다.

const [blog, isPending, error] = useFetch(`http://localhost:3001/blogs/${id}`)

 아니면 api를 문서화 하여 활용할 수도 있다. (헤더는 꼭 작성해주기 안 하니깐 데이터가 날아갔다...)

const BASE_URL = 'http://localhost:3000/';
const BLOG_URL = 'http://localhost:3000/blogs/';

// post: 새 데이터 추가(id는 자동 추가됨)
export const fetchCreate = (url, data) => {
    fetch(url, {
        method: "POST",
        headers: {
            "Content-Type" : "application/json",
            Accept: "application/json"
        },
        body: JSON.stringify(data)
    })
    .then(() => {
        window.location.href = BASE_URL; // 특정 URL로 접속했을 때 다른 URL로 이동시킴(3000으로 이동)
    })
    .catch((error) => {
        console.error('Error', error);
    })
}

// delete: 데이터 삭제(get과 마찬가지로 헤더 필수 아님)
export const fetchDelete = (url, id) => {
    fetch(`${url}${id}`, {
        method: "DELETE",
    })
    .then(() => {
        window.location.href = BASE_URL;
    })
    .catch((error) => {
        console.error('Error', error);
    })
}

// patch 기존 데이터 수정
export const fetchPatch = (url, id, data) => {
    fetch(`${url}${id}`, {
        method : "PATCH",
        headers: {
            "Content-Type" : "application/json",
            Accept: "application/json"
        },
        body: JSON.stringify(data)
    })
    .then(() => {
        window.location.href = `${BLOG_URL}${id}`;
    })
    .catch((error) => {
        console.error('Error', error);
    })
}

 

useInput

input 사용 시 상태 변경 로직을 컴스텀 훅으로 만들 수 있다.

import { useCallback, useState } from "react";

const useInput = () => {
// input 값에 대한 상태
    const [value, setValue] = useState('');

// 값과 값 변경 시 상태변경 함수 객체로 묶음
    const bind = {
        value,
        onChange : useCallback((e) => {
            const { value } = e.target; 
            setValue(value)
        }, [])
    }

    return bind;
}

export default useInput;

상태변경 함수를 useCallback으로 감싸주면 input에 입력할 때마다 새로운 함수가 생성되는 것이 아니라 기존의 함수를 재활용할 수 있다. useCallback의 종속배열이 []인 이유는 useCallback이 의존하는 변수 없기  때문이다.

 

Input을 컴포넌트로 만든다면 좀 더 코드를 정리할 수 있다.

//textarea도 multi-line input이므로 input 이름으로 범용성을 주는 게 좋음.
//혹시 헷갈릴 수 있다면 그냥 따로 component를 이 안에 만들어줘도 무방하다.

const Input = ({label, value}) => {

    //따로 컴포넌트를 만드는 경우
    const inputComponent = <>
        <input 
        type="text" 
        required 
        {...value}
        placeholder={`${label}을 입력해주세요.`}
        />
    </>

    const textAreaComponent = <>
        <textarea
        type="text" 
        required 
        {...value}
        placeholder={`${label}을 입력해주세요.`}
        />
    </>


    return (
        <>
            <label>{label}</label>
            {label === "제목" ?
                <input 
                    type="text" 
                    required 
                    {...value}
                    // useInput에서 값과 onchange를 객체로 묶어줬으므로 스프레드로 속성 입력 가능
                    placeholder={`${label}을 입력해주세요.`}
                /> : 
                <textarea
                    type="text" 
                    required 
                    {...value}
                    placeholder={`${label}을 입력해주세요.`}
                />
            }

            {/* 따로 만드는 경우 */}
            {/* <label>{label}</label>
            { label === "제목" ? inputComponent : textAreaComponent } */}
        </>
    )
}

export default Input;
 

 

useInput에서 값과 onchange를 객체로 묶어줬으므로 스프레드 문법을 활용 하면 input 속성 입력 시 좀 더 간편하게 작성할 수 있다. value={value}, onchange={e=>...} 가 리턴 값이므로 스프레드 문법으로 풀어서 사용 가능하다. 예를 들어

const CreateBlog = ({blogs}) => {
    const titleBind = useInput('');
    const bodyBind = useInput('');
    const authorBind = useInput('김코딩');

    return (
                    <Input label={"제목"} value={titleBind} />
                    <Input label={"내용"} value={bodyBind} />
    )
  }

 

useScrollTop

적용된 컴포넌트 진입시 페이지 맨 위로 스크롤해주는 커스텀 훅
App 컴포넌트에서 실행해주기
import { useEffect } from 'react';

// 적용된 컴포넌트 진입시 페이지 맨 위로 스크롤해주는 커스텀 훅
const useScrollTop = () => {
// 처음 렌더링 시 맨 위로 스크롤 이동
    useEffect(() => {
        if (window) window.scrollTo(0, 0);
    }, [])
}

export default useScrollTop;

'React' 카테고리의 다른 글

Redux Thunk 미들웨어 사용해보기  (0) 2023.04.11
to do 앱 만들기 후기  (0) 2023.04.09
리액트로 달력만들기  (0) 2023.04.08
Proxy  (0) 2023.04.04