React Hook - Custom Hooks

김소희·2025년 11월 4일

우리는 멋진 개발자들이라서 중복 코드를 보면 고치고 싶다.
우리가 만든 기능이 여러 컴포넌트에 쓰일 때,
복사-붙여넣기가 대신 마치 리액트 훅처럼 깔끔하게 쓸 수 있다면 좋겠다고 생각할 것이다.
리액트에서는 내가 원하는 인터페이스와 기능을 담은 나만의 커스텀 훅을 직접 만들 수 있다.

const[eat,setFood] = useEat("🍕")

Custom Hooks을 만드는 방법

함수명은 반드시 use 로 시작해야 한다.
그 안에 어떤 기능을 넣을지, 어떤 매개변수와 어떤 값을 반환할지는 전적으로 개발자가 자유롭게 설계할 수 있다.
만약 use 키워드가 빠진다면 React 는 이 함수를 Hook으로 인식하지 못하기 때문에, 에러가 발생했을 때도 Hook 관련 경고나 안내 메시지를 제대로 제공할 수 없다.

Custom Hook 내부에서 useState, useEffect 같은 다른 훅도 자유롭게 사용할 수 있다.
그리고 이 커스텀 훅을 사용하는 컴포넌트는 내부에서 어떤 훅들을 쓰는지 몰라도 된다.

예제코드 - useInput

import { useState } from 'react';

function App() {
  const [inputValue, setInputValue] = useState('');

  const handleChange = (e) => {
    setInputValue(e.target.value);
  };

  const handleSubmit = () => {
    alert(inputValue);
    setInputValue('');
  };

  return (
    <div>
      <h1>useInput</h1>
      <input value={inputValue} onChange={handleChange} />
      <button onClick={handleSubmit}>확인</button>
    </div>
  );
}

export default App;

컴포넌트가 처음 렌더링될 때 inputValue 라는 state 를 빈 문자열로 만들고, setInputValue 라는 setter 함수로 값을 변경할 수 있게 한다. 사용자가 input 창에 글자를 입력할 때마다 onChange 이벤트가 발생하고, handleChange 함수가 실행되면서 e.target.value 를 그대로 setInputValue 로 넣어준다. 그래서 입력하는 순간마다 state 가 업데이트되고, 이 값이 다시 input 의 value 로 반영되면서 화면에 보여진다.

버튼을 클릭하면 handleSubmit 함수가 실행된다. 이 함수에서는 현재 입력된 state 값을 alert 로 한번 보여주고, 그 다음에 다시 setInputValue('') 로 state 를 초기화해 input 을 빈 문자열로 리셋한다. 결국 입력 → state 변경 → 화면 반영 → 버튼 클릭으로 확인 → 초기화 라는 흐름을 보여준다.

여러 컴포넌트나 여러 input 태그에 쓰인다면 중복된 코드가 여기저기 생길 것이다.
그러나 훅으로 만들면 단 한줄로 사용할 수 있게 된다.

이제 인풋처리 기능을 추출해서 훅으로 만들어보자.

// useInput.js
import { useState } from 'react';

export function useInput(initialValue, submitAction) {
  const [inputValue, setInputValue] = useState(initialValue);

  const handleChange = (e) => {
    setInputValue(e.target.value);
  };

  const handleSubmit = () => {
    submitAction(inputValue);
    setInputValue("");
  };

  return [inputValue, handleChange, handleSubmit];
}

---------------------------------------------------------------
  
// App.js
import { useInput } from './useInput';

function DisplayMessage(message) {
  alert(message);
}

function App() {
  const [inputValue, handleChange, handleSubmit] =
    useInput("id를 입력해주세요", DisplayMessage);

  const [inputValue2, handleChange2, handleSubmit2] =
    useInput("pw를 입력해주세요", DisplayMessage);

  return (
    <div>
      <h1>useInput</h1>
      <input value={inputValue} onChange={handleChange} />
      <input value={inputValue2} onChange={handleChange2} />
      <button onClick={handleSubmit}>ID 확인</button>
      <button onClick={handleSubmit2}>PW 확인</button>
    </div>
  );
}

export default App;

예제코드 - useFetch

좀더 유용한 예제를 만나보자.

아래 코드는 API 요청 코드를 커스텀 훅으로 분리한 것이다.
컴포넌트마다 fetch → json → setState 를 매번 반복하지 않고,
useFetch 훅 내부에 공통 로직을 모아둔 뒤
컴포넌트에서는 단순히 fetchUrl('users') 처럼 원하는 타입만 바꿔 호출하면 된다.

초기 렌더링 시에는 initialType 으로 한 번 자동 요청되며,
요청 결과는 data 로 받아 출력할 수 있다.

// useFetch.js
import { useEffect, useState } from 'react';

export function useFetch(baseUrl, initialType) {
  const [data, setData] = useState(null);

  const fetchUrl = (type) => {
    fetch(baseUrl + '/' + type)
      .then((res) => res.json())
      .then((res) => setData(res));
  };

  useEffect(() => {
    fetchUrl(initialType);
  }, []);

  return {
    data,
    fetchUrl,
  };
}


---------------------------------------------------------------
  
// App.js
import { useFetch } from './useFetch';

const baseUrl = 'https://jsonplaceholder.typicode.com';

function App() {
  const { data, fetchUrl } = useFetch(baseUrl, 'users');

  return (
    <div>
      <h1>useFetch</h1>
      <button onClick={() => fetchUrl('users')}>Users</button>
      <button onClick={() => fetchUrl('posts')}>Posts</button>
      <button onClick={() => fetchUrl('todos')}>Todos</button>
      <pre>{JSON.stringify(data, null, 2)}</pre>
    </div>
  );
}

export default App;

예제코드 - useCoffee

마지막은 재미로 만들어보았다.

// useCoffee.js
import { useState } from "react";

export function useCoffee(defaultMenu) {
  const [coffee, setCoffee] = useState(defaultMenu);

  const order = (menu) => {
    setCoffee(menu);
  };

  const refill = () => {
    setCoffee(coffee + " ☕");
  };

  return { coffee, order, refill };
}

---------------------------------------------------

// App.jsx
import { useCoffee } from "./useCoffee";

export default function App() {
  const { coffee, order, refill } = useCoffee("아메리카노");

  return (
    <div>
      <h2>오늘 내 커피 : {coffee}</h2>

      <button onClick={() => order("라떼")}>
        라떼로 바꾸기
      </button>

      <button onClick={refill}>
        리필하기
      </button>
    </div>
  );
}

참고자료

별코딩 - 유튜브

profile
개발자 소희의 노트

0개의 댓글