15.1 커스텀 훅(Custom Hook)이란?15.2 커스텀 훅 주의사항15.2.1 주의사항15.2.2 고려사항15.3 커스텀 훅 사용해보기15.3.1 커스텀 훅 사용방법 15.3.2 간단한 커스텀 훅 만들어보기 15.4 (실습1) useTitle 15.4.1 Document Title15.4.2 useTitle 적용하기15.5 (실습2) useClick15.5.1 useClick Hook 이란?15.5.2 useClick은 왜 사용할까요?15.5.3 useClick 적용하기15.6 (실습3) useToggle 15.6.1 useToggle Hook이란?15.6.2 useToggle 적용하기15.6.3 (예제 1) useToggle Hook을 활용하여 체크박스 만들기15.6.4 (예제 2) useToggle Hook을 활용하여 리스트 출력하기15.7 useAxios
15.1 커스텀 훅(Custom Hook)이란?
커스텀 훅은 리액트 애플리케이션의 함수 컴포넌트 내 특정 로직을 분리한 함수로, 재사용 가능한 함수입니다. 커스텀 훅은 순수 자바스크립트로 코드를 작성할 때 특정 로직을 함수로 분리하고 재사용해 불필요한 코드 반복을 줄이는 것과 비슷한 맥락으로 사용됩니다.
커스텀 훅을 사용하는 이유는 다음과 같습니다.
- 여러 함수 컴포넌트에서 공통으로 사용되는 특정 컴포넌트 로직을 분리해 필요한 컴포넌트에서 호출하기 위해 사용합니다. 이를 통해 반복되는 코드를 줄일 수 있습니다.
- 리액트 프로젝트에서 특정 기능을 구현하기 위해 필요에 따라 라이브러리를 도입하기도 합니다. 하지만 원하는 기능을 구현하기에 적합한 라이브러리가 없는 경우 커스텀 훅을 직접 만들어 사용할 수 있습니다.
커스텀 훅의 장점은 다음과 같습니다.
- 재사용성
커스텀 훅을 사용하면 여러 함수 컴포넌트에서 중복되는 로직을 공유할 수 있습니다. 즉, 로직의 재사용성이 높습니다.
- 가독성
커스텀 훅을 사용하면 커스텀 훅의 내부 로직을 정확히 몰라도 어떤 값이 들어가서 어떤 결과값이 반환되는지가 명확하다는 장점이 있습니다.
fetch
를 사용해 비동기 통신을 하는 useFetch
커스텀 훅을 만들었다고 가정해 보겠습니다. useFetch
의 정확한 로직을 몰라도 단 한줄의 코드로 useFetch
가 서버에 데이터를 요청할 url을 전달받아 비동기 통신 후 받은 응답 데이터, 로딩 상태, 에러를 반환한다는 것을 유추할 수 있습니다.function App() { const url = "https://example.com/todos"; const {data: todos, isLoading, error} = useFetch(url); if (error) { return <div>{error}가 발생했습니다.</div>; } if (isLoading) { return <div>로딩중입니다..</div>; } return <TodoList todos={todos} />; } export default App;
지금까지 책에서 소개된 리액트에서 제공하는 기본 훅과 추가적인 훅들을 활용해 여러분이 원하는 커스텀 훅을 구현할 수 있습니다. 다음 챕터에서는 커스텀 훅 사용 시 주의할 점과 사용방법을 소개하고, 리액트에 내장된 훅들을 활용한 다양한 커스텀 훅 예제를 살펴보겠습니다.
15.2 커스텀 훅 주의사항
15.2.1 주의사항
커스텀 훅도 Hook이기 때문에 책의 초반부에 다루었던 Hook의 규칙에 따릅니다.
- 최상위(at the Top Level)에서만 Hook을 호출해야 합니다.
- 오직 React 함수 내에서 Hook을 호출해야 합니다.
- Hook은 use로 시작해야하는 규칙이 있습니다.
15.2.2 고려사항
- 커스텀 훅이 꼭 필요한지 확인해봅시다.
재사용될 부분이 많지 않은데 코드를 분리한다면 코드를 한눈에 이해하기 힘들 수 있습니다. 꼭 필요하지 않다면 커스텀 훅으로 분리하지 않는 것도 좋은 방법입니다.
- 커스텀 훅의 이름이 직관적인지 생각해봅시다.
Hook은 선언적으로 작성해야 합니다. 선언적이라는 것은 어떤 방법(HOW)으로 해결할지보다는 무엇(WHAT)을 해야 할지를 나타낸다고 할 수 있습니다. 예를 들어 커스텀 훅으로 만들어진
useToggle
은 무엇에 쓰일지 예상할 수 있습니다. 이렇게 “어떻게”라는 방법보다는 “무엇”을 하기 위해 만들어진 Hook인지 알 수 있어야합니다.- 만들어진 커스텀 훅이 순수한지 확인해봅시다.
우리가 선언적으로 만든 커스텀 훅이 예상한 대로 동작하기 위해선 순수해야 합니다. 여기서 순수하다는 것은, 같은 인자를 넣었을 때 같은 동작을 기대할 수 있다는 의미입니다. 또한 Hook이 사용되면서 일어날 수 있는 사이드이펙트(Side Effect)를 최소화해야 합니다. 그렇지 않다면 Hook을 사용할 때 항상 같은 결과를 장담할 수 없고 사이드이펙트를 예측할 수 없어 유지보수가 힘들어질 것입니다.
사이드이펙트(Side Effect)
컴퓨터 과학에서 함수가 결과값 이외에 다른 상태를 변경시킬 때 사이드이펙트 또는 부작용이 있다고 말합니다. 예를 들어, 함수가 전역변수나 정적변수를 수정하거나, 인자로 넘어온 것들 중 하나를 변경하거나 화면이나 파일에 데이터를 쓰거나, 다른 부작용이 있는 함수에서 데이터를 읽어오는 경우가 있습니다. 사이드이펙트는 프로그램의 동작을 이해하기 어렵게 만듭니다.
15.3 커스텀 훅 사용해보기
15.3.1 커스텀 훅 사용방법
아래 코드 예시는 위에서 사용된
useFetch
커스텀 훅입니다. import { useState, useEffect } from 'react'; const useFetch = url => { const [data, setData] = useState(null); useEffect(() => { fetch(url) .then(res => res.json()) .then(data => setData(data)) }, [url]); return [data]; }; export default useFetch;
위 코드는 매번 fetch를 통해 서버에 네트워크 요청을 보내고 새로운 정보를 받아오는 경우 반복되는 코드를 최소화하기 위해
useFetch
라는 커스텀 훅을 만들어 코드 로직의 효율성을 높일 수 있습니다. 위 코드에서 알 수 있다시피 사용방법은 다음과 같습니다. 사용방법
- 커스텀 훅의 이름은
use
로 시작되어야 합니다.
use
로 시작되지 않으면 특정한 함수가 그 안에서 Hook을 호출하는지 알 수 없기 때문입니다. 리액트 공식 홈페이지에서 강조하는 관습으로써 이를 따르지 않으면 특정한 함수가 그 안에서 Hook을 호출하는지를 알 수 없기 때문에 Hook 규칙의 위반 여부를 자동으로 체크할 수 없습니다.
- 커스텀 훅 또한 useState와 useEffect과 같은 내장 훅을 사용할 수 있습니다.
우리는
useFetch
을 직접 호출하기 때문에 리액트 관점에서 컴포넌트는 useState와 useEffect만 호출합니다. 하나의 컴포넌트에서 독립적인 useState와 useEffect를 여러 번 호출할 수 있습니다. - 같은 커스텀 훅을 사용하는 서로 다른 컴포넌트는 state를 공유하지 않습니다.
커스텀 훅은 상태 관련 로직을 재사용하는 매커니즘이지만 커스텀 훅을 사용할 때마다 그 안의 state와 effect는 완전히 독립적이기 때문에 서로 다른 컴포넌트는 독립된 state를 얻을 수 있습니다.
15.3.2 간단한 커스텀 훅 만들어보기
다양한 커스텀 훅을 만들어보기 전에 간단한 예시와 함께 커스텀 훅 사용 전과 후를 비교해보며 사용 이유에 대해 알아보겠습니다.
import { useState } from "react"; function App() { const [value, setValue] = useState(""); const handleChange = (e) => { setValue(e.target.value); }; return ( <div> <p>{value}</p> <input type="text" onChange={handleChange} /> </div> ); } export default App;

위 코드는 useState를 통해 사용자의 입력값에 따라 해당 값을 출력해주는 코드입니다. input 값이 변경될 때마다 handleChange 함수를 실행시켜 state를 업데이트하게 됩니다. 현재는 하나의 input 값만 처리하고 있기 때문에 하나의 함수로 충분하지만, 만약 여러 개의 input 값을 처리해야 한다면 중복되는 코드가 늘어날 것입니다. 이러한 경우를 방지하기 위해 input 값을 처리하는
useInput
이라는 커스텀 훅을 만들어보겠습니다.import { useState } from "react"; export const useInput = (initialValue) => { const [value, setValue] = useState(initialValue); // --- ⓵ const handleChange = (e) => { setValue(e.target.value); }; return [value, handleChange]; // --- ⓶ };
App.jsx에서 state를 업데이트하는 함수를 useInput.js로 옮겨주었습니다. ⓵에서 매개변수로 받은 값인 initialValue를 useState의 초깃값으로 설정하였습니다. 만약 초깃값을 지정할 필요가 없다면 매개변수를 지정하지 않고 사용하는 것도 가능합니다. ⓶에서 반환값은 배열로 설정하여 value의 state와 handleChange 함수를 반환하도록 하였습니다.
import { useInput } from "./useInput"; function App() { const [value, handleChange] = useInput("안녕 라이캣🦁"); // --- ⓵ return ( <div> <p>{value}</p> <input type="text" value={value} onChange={handleChange} /> // --- ⓶ </div> ); } export default App;

만들어놓은
useInput
커스텀 훅을 App.jsx에서 사용하기 위해 import 합니다. ⓵에서 useInput
에서 반환하는 값을 배열로 받아오고, 인자로 ‘안녕 라이캣🦁’을 초깃값으로 설정하였습니다. input에 받아온 value와 handleChange 함수를 설정한 것을 ⓶에서 확인할 수 있습니다. 실행해보면 초깃값이 적용된 것과 입력값에 따라 출력값이 변경되는 것을 확인할 수 있습니다.만약 하나의 컴포넌트에 여러 개의 input을 사용해야 한다면 아래와 같이 적용할 수 있습니다.
import { useInput } from "./useInput"; function App() { const [value, handleChange] = useInput("안녕 라이캣🦁"); const [value2, handleChange2] = useInput(""); // --- ⓵ return ( <div> <p>{value}</p> <input type="text" value={value} onChange={handleChange} /> <p>{value2}</p> <input type="text" onChange={handleChange2} /> // --- ⓶ </div> ); } export default App;

위 예제에서 만든 input 아래에 초깃값을 지정하지 않은 input을 하나 더 추가해보겠습니다. ⓵에서는 위에서 만든 input이 받는 변수와 다른 변수명으로 지정하여
useInput
의 반환값을 받아왔습니다. 실행해보면 두 개의 input이 각각 처리되는 것을 확인할 수 있습니다. 이처럼 여러 개의 컴포넌트 또는 하나의 컴포넌트에서 useInput
을 반복적으로 사용하더라도 모든 input은 독립적인 state를 가지게 되며, 간편하게 사용이 가능합니다. 커스텀 훅 사용 전후를 살펴봄으로써 우리는 커스텀 훅을 통해 중복되는 코드가 늘어나는 것을 방지하며, 필요한 부분에 간편하게 사용이 가능하다는 것을 알 수 있었습니다. 이제 다양한 커스텀 훅을 직접 만들어보겠습니다.
15.4 (실습1) useTitle
15.4.1 Document Title
Document Title
웹 사이트에 접속하면 브라우저의 상단 탭에서 현재 페이지의 제목을 볼 수 있으며 해당 페이지의 제목을 Document Title이라고 합니다. Document Title은 가장 대표적인 웹 페이지의 타이틀로, 각 페이지의 주제를 가장 잘 나타낼 수 있는 요소이자 해당 웹 페이지를 사용하는 유저에게 현재 자신이 보고 있는 웹 페이지가 어떤 페이지인지를 알려주는 중요한 요소입니다.

하지만 React는 SPA이므로 CSS, JS 등의 번들이 포함된 하나의 index.html 파일만 가지기 때문에 각 페이지마다 Document Title을 변경하는데 한계점이 있습니다. 이를 위해 현재 페이지의 title을 바꿔주기 위한 useTitle 커스텀 훅을 활용한다면 페이지마다 Document Title을 변경할 수 있습니다.
15.4.2 useTitle 적용하기
import { useEffect } from 'react'; const useTitle = (title) => { useEffect(() => { const prevTitle = document.title; document.title = title; return () => (document.title = prevTitle); }, [title]); }; export default useTitle;
useTitle은 title을 매개변수로 전달 받는 커스텀 훅입니다. 새로운 컴포넌트가 mount 되고, 해당 컴포넌트에서 useTitle을 호출하는 동시에 변경하고자 하는 title을 전달하면, useTitle 내부의 useEffect에서는 title이 변경되었으므로 내부의 코드를 실행시킵니다. useEffect 내부에서는 Document Title을 입력 받은 title로 변경하고, 해당 컴포넌트가 사라질 때 동작할 Document Title을 원래대로 돌려놓기 위한 cleanup 함수를 반환합니다.
import useTitle from './useTitle'; function Login() { useTitle('Login - React'); return <div>Login 페이지</div> } export default Login;

그리고 위 코드처럼 useTitle 커스텀 훅을 Login 컴포넌트에 적용하면 Login 페이지를 로드했을 때 Document Title이 ‘Login - React’로 변경된 것을 확인할 수 있습니다.
또한, Document Title을 설정할 때에는 사용자가 어느 사이트에 접속한 것인지는 인지하고 있는 상태이므로 Login 페이지처럼 서브 페이지 이름이 앞에 나오는 것이 사용자 피로도를 낮출 수 있습니다.
15.5 (실습2) useClick
앞서 8.1 useRef 에서 useRef는 .current 프로퍼티에 변경 가능한 값을 담고 있는 “상자” 와
같다는 것을 배웠습니다. 특정 DOM에 접근해서 제어하거나, 렌더링과 관계없이 값을 변경하고 싶을 때 사용하는 훅이었는데요. useClick 커스텀 훅을 만들 때 가장 필요한 훅입니다.
더불어 useEffect 또한 첫 렌더링이 되었을 때와 컴포넌트가 언마운트 되었을 때 이벤트를 추가, 삭제 해줘야하므로 useClick 커스텀 훅에서 매우 중요한 훅이라 할 수 있겠습니다.
15.5.1 useClick Hook 이란?
특정 요소에 대한 클릭 이벤트를 관리할 때 사용하는 커스텀 훅 입니다.
useRef
를 사용하여 주어진 함수에 대한 클릭을 추적하거나 onClick
이벤트를 추가하기 위해 사용합니다. useClick
을 사용하면 클릭을 처리하는 로직을 별도의 함수로 분리하여 컴포넌트를 보다 깔끔하고 체계적으로 유지할 수 있습니다.다시말해 클릭 이벤트로 자주 사용하는 함수가 있다면,
onClick
속성값으로 입력하는 대신에 useRef
를 사용함으로써 해당 요소를 클릭했을 때 이벤트 함수가 실행될 수 있도록 만들어주는 것입니다.15.5.2 useClick은 왜 사용할까요?
컴포넌트마다 DOM 요소의 클릭이벤트를 줘야 한다면 각 컴포넌트에
onClick
이벤트를 입력해주어야 하는 반복 작업이 이루어지게 될 것입니다. 이러한 비효율적인 반복 작업보다 ref 속성값을 부여하여 이벤트를 발생시킨다면 코드를 더욱 간결하게 쓸 수 있고 유지보수 또한 편리하게 될 것입니다.15.5.3 useClick 적용하기
useClick
이 어떻게 쓰이는지, 아래 사용 예제를 통해 간단하게 살펴보겠습니다.const useClick = (onClick) => { if(typeof onClick !== "function"){ return; } }
useClick
커스텀 훅을 선언하고, 파라미터로 onClick
이라는 이름을 붙였습니다. onClick
에 조건식을 걸어서 함수가 아니라면 return하여 함수의 동작을 끝내도록 하고 다른 실행문이 동작하지 않도록 작성했습니다.아래 예제처럼
useRef
를 사용하여 요소(element)를 클릭했을 때 이벤트 함수가 동작할 수 있도록 만들어 보도록 하겠습니다.useEffect(()=>{ if(element.current){ element.current.addEventListener("click", onClick); } return () => { if (element.current){ element.current.removeEventListener("click", onClick); } } }, [])
useEffect
를 사용한 이유는 컴포넌트가 마운트 상태일 때만 이벤트를 등록하도록 하여 재렌더링이 될 때마다 새롭게 이벤트가 등록되는 것을 방지하기 위함입니다.element.current
가 있을 때에만 이벤트 함수가 생성되어야 하며, 조건부 렌더링을 하지 않으면 react의 동작원리에 따라 이벤트가 추가되지 않기 때문에 element.current
가 있을때에만 useEffect
가 실행되도록 조건문을 추가했습니다.컴포넌트가 언마운트 상태일 땐 이벤트 함수가 사라지도록 clean-up하는
useEffect
문법을 사용하였습니다. 프로젝트 볼륨이 커지면 이벤트 충돌이 발생할 수 있으며, 오래된 브라우저 환경이라면 메모리 누수가 발생할 수 있기 때문입니다.아래는 useClick 사용 예제 코드입니다.
import { useEffect } from 'react'; import { useRef } from 'react'; const useClick = (onClick) => { if(typeof onClick !== "function"){ return; } const element = useRef(); useEffect(()=>{ if(element.current){ element.current.addEventListener("click", onClick); } return () => { if (element.current){ element.current.removeEventListener("click", onClick); } } }, []) return element; } export default useClick
import React from 'react' import useClick from './useClick'; function App() { const repeatedFunction = () => console.log("say hello"); const title = useClick(repeatedFunction); return ( <div> <h1 ref={title}>Hi</h1> </div> ) } export default App
완성된
useClick
커스텀 훅을 사용해보겠습니다.위의 예제에서
useClick
의 인자로 들어가는 repeatedFunction
은 해당 컴포넌트를 클릭했을 때 반복적으로 동작되는 함수입니다.title
은 useClick(sayHello)
을 담는 변수이며 ref 속성 값으로 입력됩니다. 입력된 요소를 클릭했을 때 repeatedFunction
이 클릭 이벤트 함수로 동작하게 됩니다.
지금까지 작성한 코드를 가지고
useClick
커스텀 훅이 잘 동작하는지 브라우저 환경에서 실행시켜보았습니다.이처럼 컴포넌트를 반복적으로 클릭했을 때 동일한 함수가 클릭이벤트로 동작되어야 한다면
useClick
커스텀 훅을 이용하여 ref 속성 하나만으로도 편리하게 이벤트 함수를 실행시키는 모습을 보실 수 있습니다.15.6 (실습3) useToggle
15.6.1 useToggle Hook이란?
토글이란 두 가지 상태만을 가지고 있어 서로 다른 값으로 전환할 수 있는 기능입니다. 이렇게 두루 사용되는 토글 기능을 일일이 동일한 로직을 반복하여 쓰는 것보다 커스텀 훅으로 만들어 사용한다면 유용하게 재사용할 수 있습니다. useToggle Hook은 체크박스는 물론 다크 모드 전환이나 모달 창 열기 등의 전환이 가능한 작업을 수행하려는 경우에 유용하게 사용할 수 있는 훅 입니다.
토글 로직의 재사용을 위한 useToggle 커스텀 훅은 아래와 같이 구현할 수 있습니다.
import { useState } from "react"; const useToggle = (initialValue = false) => { const [value, setValue] = useState(initialValue); const toggleValue = () => { setValue(!value) }; return [value, toggleValue]; }; export default useToggle;
useToggle Hook을 사용하면 토글을 관리하는 코드마다 동일한 로직을 반복할 필요 없이 useToggle Hook을 가져와서 재사용할 수 있습니다. useToggle Hook은 매개변수로 true 또는 false인 boolean 값을 전달하여 상태를 반대 값으로 전환 시킵니다. 매개변수로는 초깃값을 받으며, 값이 없을 경우 기본값으로 false가 지정됩니다.
그리고 현재 토글의 상태를 나타내는 state 변수와 토글의 상태를 전환시키는 함수를 설정하여 이를 배열로 반환합니다. 그렇기 때문에 useToggle Hook을 사용하면 토글을 설정하려는 상태값을 기억하고 이를 전달할 수 있습니다.
15.6.2 useToggle 적용하기
useToggle 훅을 사용하여 체크박스를 클릭 함에 따라 텍스트가 변경되는 첫 번째 예제와 버튼을 클릭 함에 따라 리스트를 확인하고 숨길 수 있는 두 번째 예제를 통해 useToggle 훅에 대해 알아보겠습니다.
먼저 useToggle 훅을 import 해서 사용할 수 있도록 useToggle.js 파일을 하나 새로 생성합니다.
import { useState } from "react"; const useToggle = (initialValue = false) => { const [value, setValue] = useState(initialValue); }
useToggle 함수는 boolean 값을 매개변수로 사용하여 초깃값을 전달합니다. 토글의 상태를 나타내는 value라는 state 변수와 이 state 변수를 관리하는 함수 setValue를 배열에 담아 선언해 주었습니다. 그리고 useState Hook의 인자로 초깃값을 전달해 주고, useState를 Import 해줍니다.
이제 토글의 상태를 나타내는 value와 토글의 상태를 전환시키는 함수 setValue를 통해 토글의 상태를 변경할 수 있습니다.
import { useState } from "react"; const useToggle = (initialValue = false) => { const [value, setValue] = useState(initialValue); const toggleValue = () => { setValue(!value) }; return [value, toggleValue]; };
다음으로는 useToggle을 핸들링하는 toggleValue라는 함수를 만들어주겠습니다. toggleValue 함수에는 콜백 함수로 토글의 상태를 전환시키는 함수 setValue 안에 토글의 상태를 나타내는 state 변수 value를 담아 값을 갱신합니다. 이를 통해 toggleValue 함수는 토글의 상태를 반대 값으로 전환시키는 스위치 역할을 하게 됩니다.
그리고 state 변수 value와 toggleValue 함수를 배열에 담아 반환합니다.
import { useState } from "react"; const useToggle = (initialValue = false) => { const [value, setValue] = useState(initialValue); const toggleValue = () => { setValue(!value) }; return [value, toggleValue]; }; export default useToggle;
그리고 마지막으로 useToggle을 export 해주면 App.js 파일에서 useToggle 훅을 사용할 수 있습니다.
useToggle 커스텀 훅을 완성시켰으니 이제 App.js 파일에서 적용해 보겠습니다. 우리는 체크박스를 클릭 함에 따라 텍스트가 변경되는 코드와 버튼을 클릭 함에 따라 리스트를 확인하고 숨길 수 있는 코드를 작성해야 하므로, App 함수 안에 서로 다른 상태 값이 담긴 useToggle Hook을 두 번 사용하겠습니다.
import useToggle from "./useToggle"; function App() { const [isChecked, setIsChecked] = useToggle(); const [isListOpen, setListOpen] = useToggle(); return; } export default App;
위와 같이 체크박스에 사용될 isChecked 변수와 리스트를 확인하는 버튼에 사용될 isListOpen이라는 state 변수를 각각 선언해 주었습니다. useToggle의 초깃값은 값이 비어있을 경우 false가 기본값으로 지정됩니다.
15.6.3 (예제 1) useToggle Hook을 활용하여 체크박스 만들기
function App() { const [isChecked, setIsChecked] = useToggle(); return ( <> <h3>음료를 주문하시겠어요? 🥤</h3> <label for='check'> <input type='checkbox' id='check' onChange={setIsChecked} /> {isChecked ? "네" : "아니요"} </label> </> ); }
먼저 사용자에게 정보를 입력받을 수 있도록 input 태그를 작성하고 체크 박스를 생성하기 위해 type 속성을 ‘checkbox’로 지정해 주었습니다. 그리고 체크박스를 클릭 함에 따라 텍스트를 변경시켜주기 위해 onChange 이벤트에 setIsChecked 함수를 추가해 주었고, 체크박스를 클릭할 때마다 isChecked의 값이 바뀌어 텍스트가 변경될 것입니다.

실행 화면을 확인해보면 위와 같이 체크박스를 선택하기 전까지는 “아니요”라는 텍스트가 확인됩니다.

체크박스를 선택하면 위와 같이 “네”라는 텍스트로 변경되며, 체크박스를 클릭할 때마다 텍스트가 반복적으로 변경되는 것을 확인할 수 있습니다. 이렇게 useToggle Hook을 사용하여 체크박스를 클릭 함에 따라 텍스트가 변경되는 첫 번째 예제를 확인해 보았습니다.
이번에는 useToggle Hook을 사용하여 버튼을 클릭할 때마다 리스트를 확인하고 숨길 수 있는 예제를 확인해 보겠습니다.
15.6.4 (예제 2) useToggle Hook을 활용하여 리스트 출력하기
function App() { const [isListOpen, setListOpen] = useToggle(); return ( <div> <h3>오늘의 추천 음료를 확인해주세요 🥤</h3> <button onClick={setListOpen}>오늘의 추천 음료 확인하기</button> {isListOpen && ( <ul> <li>달콤한 라이캣프라푸치노</li> <li>맛있는 개리쉐이크</li> <li>스파클링 웨이드에이드</li> <li>상큼한 빙키주스</li> </ul> )} </div> ); }
버튼 태그에 onClick 이벤트를 주어 setListOpen 함수를 추가해 주었습니다. 그리고 중괄호 안에 논리 연산자를 사용하여 state 변수 isListOpen과 ul 태그 안에 감싸인 li 태그들을 함께 작성해서 조건부로 렌더링 될 수 있도록 하였습니다. 이제 이벤트 핸들러가 포함된 버튼이 클릭될 때마다 토글의 상태가 전환되어 리스트가 보이기도, 숨겨지기도 할 것입니다. 실행 결과를 확인해 보겠습니다.

버튼을 클릭하기 전에는 위와 같이 리스트가 확인되지 않습니다. 그러나 버튼을 클릭하면 아래와 같이 음료 리스트가 보이는 것을 확인할 수 있습니다. 이벤트 핸들러가 포함된 버튼이 클릭될 때마다 반복적으로 토글의 상태가 스위치 역할을 하며 반대 값으로 전환됩니다.

지금까지 useToggle Hook을 사용하여 버튼을 클릭 함에 따라 리스트가 보이고 숨겨지는 두 번째 예제까지 확인해 보았습니다. 이처럼 서로 다른 반대 값으로 전환하는 작업을 수행하려는 경우, useToggle 커스텀 훅을 만들어 재사용 한다면 토글을 관리하는 코드마다 동일한 로직을 반복할 필요 없이 간단하게 구현할 수 있습니다.
위에서 작성했던 두 예제 코드를 App 함수에 같이 작성해 보면 아래와 같은 실행 화면을 확인할 수 있으며 최종 코드는 하단에서 확인할 수 있습니다.

최종 코드
import useToggle from "./useToggle"; function App() { const [isChecked, setIsChecked] = useToggle(); const [isListOpen, setListOpen] = useToggle(); return ( <> <h3>음료를 주문하시겠어요? 🥤</h3> <label for='check'> <input type='checkbox' id='check' onChange={setIsChecked} /> {isChecked ? "네" : "아니요"} </label> <div> <h3>오늘의 추천 음료를 확인해주세요 🥤</h3> <button onClick={setListOpen}>오늘의 추천 음료 확인하기</button> {listOpen && ( <ul> <li>달콤한 라이캣프라푸치노</li> <li>맛있는 개리쉐이크</li> <li>스파클링 웨이드에이드</li> <li>상큼한 빙키주스</li> </ul> )} </div> </> ); } export default App;
import { useState } from "react"; const useToggle = (initialValue = false) => { const [value, setValue] = useState(initialValue); const toggleValue = () => { setValue(!value) }; return [value, toggleValue]; }; export default useToggle;
15.7 useAxios
먼저 axios란, 클라이언트가 웹서버에 사용자 요청의 목적/종류를 알리는 수단인 HTTP 메서드을 이용해 데이터 통신을 할 수 있는 라이브러리입니다.
HTTP 메서드에는 get,post,put,delete와 같이 많이 사용하는 메서드가 있습니다.
get
: 데이터를 조회할 때 사용합니다.
post
: 새로운 데이터를 추가할 때 사용합니다.
put
: 기존의 데이터를 수정할 때 사용합니다.
delete
: 기존의 데이터를 삭제할 때 사용합니다.
예를 들어, 새로운 데이터를 추가할 때는 axios 라이브러리를 사용하여 다음과 같이 작성합니다.
첫 번째 인자는 url, 그리고 두 번째 인자는 headers와 추가할 데이터를 담은 객체 형식의 데이터를 작성합니다.
https://jsonplaceholder.typicode.com/ 는 백엔드 서버가 없을 때 임시로 가상 데이터를 만들어 api를 테스트해 볼 수 있는 사이트입니다. POST, PUT, PATCH, DELETE 요청은 실제로 이루어지지는 않지만 마치 가짜인 것처럼 표시가되며, GET 요청의 경우 https://jsonplaceholder.typicode.com/ 에서 제공하는 데이터를 가져올 수 있습니다.
axios.post(`https://jsonplaceholder.typicode.com/posts`, { headers: { accept: '*/*' }, data: { userId: 1, id: 19392, title: 'title', body: 'Sample text', }, })
만약, 다음과 같이 axios의 메서드를 적지 않는다면 기본적으로 get 메소드가 실행되고
두 번째 인자의 프로퍼티로 method를 작성하면 위와 같은 결괏값을 얻을 수 있습니다.
두번째 인자의 프로퍼티로 method를 작성하면 위와 동일한 결과값을 얻을 수 있습니다.
axios(`https://jsonplaceholder.typicode.com/posts`, { method: 'POST', headers: { accept: '*/*' }, data: { userId: 1, id: 19392, title: 'title', body: 'Sample text', }, })
그렇다면 다음 예제를 통해 실제로 axios를 활용해 요청을 해보도록 하겠습니다.
import axios from "axios"; import { useEffect, useState } from "react"; function App() { const [response, setResponse] = useState(null); const [error, setError] = useState(""); const [loading, setLoading] = useState(true); const fetchData = async () => { try { const res = await axios({ method: "get", url: "https://jsonplaceholder.typicode.com/posts", }); console.log(res); setResponse(res.data); } catch (error) { setError(error); } finally { setLoading(false); } }; useEffect(() => { fetchData(); }, []); return ( <div className="app"> {loading ? ( <div>Loading...</div> ) : ( <> <h3>게시글 목록</h3> <ul> {response && response.map((res, index) => ( <li key={index}> <h4> 제목 :{error && error.message} {res && res.title} </h4> <p>{res.body}</p> </li> ))} </ul> </> )} </div> ); } export default App;
App.jsx
위의 예제를 보면 fetchData를 통해 url로부터 데이터를 가져오고 있습니다. 그 뒤 받아온 데이터를 아래와 같이 화면에 렌더링해주고 있습니다.

예제와 같이 단 한 번 axios 요청이 있다면 상관이 없겠지만 비슷한 요청이 여러 개의 컴포넌트에서 실행된다면 어떻게 될까요?? 같은 코드가 컴포넌트마다 생성될 것이고 이는 매우 비효율적이라고 볼 수 있습니다. 이처럼 API를 호출하는 코드가 여러 번 반복되는 것을 막아주고 재사용할 수 있도록 만드는 커스텀 훅이 useAxios입니다. 이어지는 예제에서 기존에 작성하였던 코드를 커스텀 훅으로 만들어 사용하는 방법에 대해 알아보겠습니다.
- useAxios 컴포넌트
import { useState, useEffect } from 'react'; import axios from 'axios'; axios.defaults.baseURL = 'https://jsonplaceholder.typicode.com'; function useAxios(params) { const [response, setResponse] = useState(null); const [error, setError] = useState(''); const [loading, setLoading] = useState(true); const fetchData = async (params) => { try{ const res = await axios(params); setResponse(res.data); }catch(err) { setError(err); } finally{ setLoading(false); }; }; useEffect(() => { fetchData(params); }, []); return { response, error, loading }; }; export default useAxios;
useAxios.jsx
위의 useAxios 컴포넌트는 렌더링이 될 때 최초로 한 번 fetchData함수를 실행하며 response, error, loading을 return 하고 params라는 인자를 받습니다. response는 요청방식에 따른 데이터가 되고, error는 요청에 성공하지 못하였을 때 에러 메시지가 됩니다. 또한 loading은 요청이 완료되기 전까지 사용자에게 보여질 메시지를 컨트롤하는 역할을 합니다.
- useAxios를 활용하여 바꾸어준 예제
import useAxios from './useAxios'; function App() { const { response, error, loading } = useAxios({ method: 'GET', url: '/posts', }); // {url, method, body, headers}로 구성된 객체를 생성하고 useAxios 에 인수를 전달하여 HTTP 호출을 수행합니다. return ( <div className="app"> {loading ? ( <div>Loading...</div> ) : ( <> <h1>게시글 목록</h1> <ul> {response && response.map( (res, index) => <li key={index}> <h1> 제목 : {error && error.message} {res && res.title} </h1> <p>{res.body}</p> </li> )} </ul> </> )} </div> ); }; export default App;
App.jsx
위와 같이 기존에는 한 컴포넌트에서 존재하던 axios 요청을 useAxios로 만들어 사용해주었습니다. 한눈에 보아도 코드가 매우 깔끔해진 것을 볼 수 있습니다. 예제에서 보이듯 커스텀 훅을 통해 같은 코드를 줄일 수 있으며 이는 곧 유지보수에도 굉장히 용이해집니다. 이와같이 커스텀 훅을 활용하면 반복되는 코드를 줄일 수 있습니다.