새소식

반응형
Front-end

[React] 알아두면 쓸 데 있는 React Custom Hooks 10가지

2024.11.19
  • -
반응형

1. 들어가며

React 개발을 하다보면 custom hooks를 구현해주어야 할 일이 많이 있는데요. 커스텀 훅을 사용하면 개발자가 여러 컴포넌트에서 로직을 재사용할 수 있기 때문에 코드를 더 유지보수하기 쉽고, 모듈화하여 읽기 쉽게 만들 수 있다는 장점이 있습니다.

 

다음에 나올 10가지의 커스텀 훅은 실제로 많이 사용되는 것들이며, 경우에 따라 유용한 커스텀 훅이 있으니 참고하시면 좋을 것 같습니다.

 

2. useFetch

useFetch 훅은 일반적으로 API에서 데이터를 가져오는 데 사용될 수 있습니다. loading, error 및 response status를 관리하며 데이터 검색에 필요한 모든 로직들을 캡슐화하고 있습니다.

 

import { useState, useEffect } from 'react';

function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await fetch(url);
        const result = await response.json();
        setData(result);
      } catch (err) {
        setError(err);
      } finally {
        setLoading(false);
      }
    };
    fetchData();
  }, [url]);

  return { data, loading, error };
}

 

3. usePrevious

usePrevious는 state나 props의 이전 값을 저장하기에 애니메이션, form data, 혹은 복잡한 state 관리를 하는 경우 유용합니다.

 

import { useRef, useEffect } from 'react';

function usePrevious(value) {
  const ref = useRef();

  useEffect(() => {
    ref.current = value;
  }, [value]);

  return ref.current;
}

 

 

사용:

const previousCount = usePrevious(count);

 

 

4. useToggle

간단한 커스텀 훅인 useToggle은 boolean 상태를 효율적으로 관리하여 테마, 모달 및 UI 요소를 토글하는 데 유용합니다.

 

import { useState } from 'react';

function useToggle(initialValue = false) {
  const [value, setValue] = useState(initialValue);

  const toggle = () => setValue(prev => !prev);

  return [value, toggle];
}

 

 

사용:

const [isToggled, toggle] = useToggle();

 

 

5. useLocalStoarge

useLocalStorage는 브라우저의 로컬 스토리지에 저장된 값을 설정, 가져오기 및 업데이트 할 수 있는 훅으로, 영구적인 데이터 저장을 위해 사용할 수 있습니다.

 

import { useState } from 'react';

function useLocalStorage(key, initialValue) {
  const [storedValue, setStoredValue] = useState(() => {
    try {
      const item = localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      console.log(error);
      return initialValue;
    }
  });

  const setValue = value => {
    try {
      setStoredValue(value);
      localStorage.setItem(key, JSON.stringify(value));
    } catch (error) {
      console.log(error);
    }
  };

  return [storedValue, setValue];
}

 

사용:

const [name, setName] = useLocalStorage('name', 'Guest');

 

 

6. useDebounce

debounce 작업은 이벤트의 빠른 트리거를 방지해 줍니다. useDebounce를 통해 입력 필드나 검색창에 유용하게 활용할 수 있습니다.

import { useState, useEffect } from 'react';

function useDebounce(value, delay) {
  const [debouncedValue, setDebouncedValue] = useState(value);

  useEffect(() => {
    const handler = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    return () => clearTimeout(handler);
  }, [value, delay]);

  return debouncedValue;
}

 

사용:

const debouncedSearchTerm = useDebounce(searchTerm, 500);

 

 

7. useInterval

useInterval은 react의 라이프사이클과 굉장히 잘 통합되며, 간격을 설정하면 그 간격마다 반복 작업을 하도록 관리할 수 있습니다.

 

import { useEffect, useRef } from 'react';

function useInterval(callback, delay) {
  const savedCallback = useRef();

  useEffect(() => {
    savedCallback.current = callback;
  }, [callback]);

  useEffect(() => {
    if (delay !== null) {
      const id = setInterval(() => savedCallback.current(), delay);
      return () => clearInterval(id);
    }
  }, [delay]);
}

 

 

사용:

useInterval(() => {
  setCount(prevCount => prevCount + 1);
}, 1000);

 

8. useWindowSize

useWindowSize를 사용하면 viewport의 크기에 따라 컴포넌트를 조정할 수 있어 반응형 디자인에 유용합니다.

import { useState, useEffect } from 'react';

function useWindowSize() {
  const [windowSize, setWindowSize] = useState({
    width: window.innerWidth,
    height: window.innerHeight,
  });

  useEffect(() => {
    const handleResize = () => {
      setWindowSize({
        width: window.innerWidth,
        height: window.innerHeight,
      });
    };
    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, []);

  return windowSize;
}

 

사용:

const { width, height } = useWindowSize();

 

9. useOnclickOutside

useOnlickOutside는 모달, 드롭다운 또는 그 외부를 클릭할 때 닫아야 하는 모든 컴포넌트를 닫을 때 유용합니다.

import { useEffect } from 'react';

function useOnClickOutside(ref, handler) {
  useEffect(() => {
    const listener = event => {
      if (!ref.current || ref.current.contains(event.target)) return;
      handler(event);
    };
    document.addEventListener('mousedown', listener);
    document.addEventListener('touchstart', listener);
    return () => {
      document.removeEventListener('mousedown', listener);
      document.removeEventListener('touchstart', listener);
    };
  }, [ref, handler]);
}

 

 

사용:

const modalRef = useRef();
useOnClickOutside(modalRef, () => setIsOpen(false));

 

10. useHover

useHover를 사용하면 요소 위로 마우스를 가져가는 것을 감지하여 툴팁이나 애니메이션, 또는 마우스를 가져간 상태에 따른 UI 피드백을 적용시킬 수 있습니다.

 

import { useState, useRef, useEffect } from 'react';

function useHover() {
  const [hovered, setHovered] = useState(false);
  const ref = useRef(null);

  useEffect(() => {
    const handleMouseOver = () => setHovered(true);
    const handleMouseOut = () => setHovered(false);
    const node = ref.current;
    if (node) {
      node.addEventListener('mouseover', handleMouseOver);
      node.addEventListener('mouseout', handleMouseOut);
    }
    return () => {
      if (node) {
        node.removeEventListener('mouseover', handleMouseOver);
        node.removeEventListener('mouseout', handleMouseOut);
      }
    };
  }, [ref]);

  return [ref, hovered];
}

 

사용:

const [hoverRef, isHovered] = useHover();

 

 

11. useMediaQuery

useMediaQuery는 css의 media queries를 수신하여 디바이스 크기에 따라 특정 스타일이나 동작을 적용할 수 있도록 합니다.

import { useEffect, useState } from 'react';

function useMediaQuery(query) {
  const [matches, setMatches] = useState(false);

  useEffect(() => {
    const mediaQuery = window.matchMedia(query);
    setMatches(mediaQuery.matches);

    const handler = event => setMatches(event.matches);
    mediaQuery.addEventListener('change', handler);

    return () => mediaQuery.removeEventListener('change', handler);
  }, [query]);

  return matches;
}

 

사용:

const isLargeScreen = useMediaQuery('(min-width: 1024px)');

 

12. 보너스

useWebSocket

useWebSocket 훅은 앱을 웹소켓 서버에 연결하여 실시간 데이터를 쉽게 주고받을 수 있게 해줍니다.

 

재연결(reconnection), 메시지 버퍼링(message buffering) 및 이벤트 기반 처리를 처리하므로 채팅 앱, 실시간 알림 또는 실시간 데이터 업데이트와 같은 애플리케이션에 사용하기 굉장히 적합합니다. 이 예시는 연결 관리, 이벤트 처리 및 깔끔한 정리를 위한 기능을 갖춘 재사용 가능한 WebSocket 훅을 보여줍니다.

 

import { useState, useEffect, useRef, useCallback } from 'react';

function useWebSocket(url, options = {}) {
  const { reconnect = true, reconnectInterval = 5000, onOpen, onMessage, onError, onClose } = options;
  const [isConnected, setIsConnected] = useState(false);
  const [lastMessage, setLastMessage] = useState(null);
  const websocketRef = useRef(null);
  const reconnectTimeout = useRef(null);

  const connect = useCallback(() => {
    websocketRef.current = new WebSocket(url);

    websocketRef.current.onopen = (event) => {
      setIsConnected(true);
      onOpen && onOpen(event);
    };

    websocketRef.current.onmessage = (event) => {
      setLastMessage(event.data);
      onMessage && onMessage(event);
    };

    websocketRef.current.onerror = (event) => {
      onError && onError(event);
    };

    websocketRef.current.onclose = (event) => {
      setIsConnected(false);
      onClose && onClose(event);
      if (reconnect) {
        reconnectTimeout.current = setTimeout(connect, reconnectInterval);
      }
    };
  }, [url, reconnect, reconnectInterval, onOpen, onMessage, onError, onClose]);

  const sendMessage = useCallback((message) => {
    if (isConnected && websocketRef.current) {
      websocketRef.current.send(message);
    }
  }, [isConnected]);

  useEffect(() => {
    connect();

    return () => {
      if (websocketRef.current) {
        websocketRef.current.close();
      }
      clearTimeout(reconnectTimeout.current);
    };
  }, [connect]);

  return { isConnected, sendMessage, lastMessage };
}

 

 

  • useWebSocket의 기능
    • 재연결 처리(Reconnection handling): 연결이 닫히면 지정된 간격 후에 훅이 자동으로 재연결을 시도합니다.
    • 이벤트 핸들링(Event Handling): 필요에 따라 처리하기 위해 onOpen, onMessage, onError, onClose 이벤트에 대한 콜백을 받습니다. 
    • 메시지 전송(message sending): 웹소켓이 열려 있을 때만 메시지를 전송하는 sendMessage 함수를 제공합니다.
    • 최근 메시지 저장소(Last message storge): 최근에 받은 메시지를 저장하여 재가입하지 않고도 가장 최근 데이터에 쉽게 액세스할 수 있습니다.

 

예시:

라이브 채팅이나 실시간 알림 시스템과 같은 컴포넌트에서 useWebSocket을 사용하는 방법은 다음과 같습니다.

 

function ChatApp() {
  const { isConnected, sendMessage, lastMessage } = useWebSocket('ws://localhost:4000/chat', {
    reconnect: true,
    reconnectInterval: 3000,
    onOpen: () => console.log('Connected to WebSocket'),
    onMessage: (event) => console.log('New message received:', event.data),
    onClose: () => console.log('Disconnected from WebSocket'),
  });

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

  const handleSend = () => {
    sendMessage(inputValue);
    setInputValue('');
  };

  return (
    <div>
      <h3>WebSocket Chat</h3>
      <div>
        <p>{isConnected ? 'Connected' : 'Disconnected'}</p>
        <input
          type="text"
          value={inputValue}
          onChange={(e) => setInputValue(e.target.value)}
          placeholder="Type your message..."
        />
        <button onClick={handleSend} disabled={!isConnected}>
          Send
        </button>
      </div>
      <div>
        <h4>Last Message:</h4>
        <p>{lastMessage}</p>
      </div>
    </div>
  );
}

 

useInfiniteScroll

useInfiniteScroll 훅은 소셜 미디어 피드나 검색 결과에서 흔히 볼 수 있는 패턴으로 사용자가 아래로 스크롤할 때 데이터를 가져오는 데 유용합니다. 로딩, 오류 처리, 페이지네이션과 같은 복잡한 상태를 처리할 수 있습니다.

 

import { useState, useEffect, useRef, useCallback } from 'react';

function useInfiniteScroll(fetchData, options = {}) {
  const { threshold = 0.8, hasMore = true } = options;
  const [data, setData] = useState([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  const observerRef = useRef();

  const loadMore = useCallback(async () => {
    if (loading || !hasMore) return;
    setLoading(true);
    setError(null);
    try {
      const newData = await fetchData();
      setData((prevData) => [...prevData, ...newData]);
    } catch (err) {
      setError(err);
    } finally {
      setLoading(false);
    }
  }, [fetchData, loading, hasMore]);

  useEffect(() => {
    if (!hasMore) return;
    
    const observer = new IntersectionObserver(
      (entries) => {
        if (entries[0].isIntersecting) {
          loadMore();
        }
      },
      { threshold }
    );
    
    if (observerRef.current) observer.observe(observerRef.current);
    
    return () => {
      if (observerRef.current) observer.unobserve(observerRef.current);
    };
  }, [loadMore, hasMore, threshold]);

  return { data, loading, error, observerRef };
}

 

 

사용:

function InfiniteScrollList() {
  const fetchMoreData = async () => {
    const response = await fetch('/api/data'); // Example API
    return response.json();
  };
  
  const { data, loading, error, observerRef } = useInfiniteScroll(fetchMoreData, {
    threshold: 0.9,
    hasMore: true
  });
  
  return (
    <div>
      {data.map((item, index) => (
        <div key={index}>{item}</div>
      ))}
      <div ref={observerRef} style={{ height: '1px' }} />
      {loading && <p>Loading...</p>}
      {error && <p>Error loading data...</p>}
    </div>
  );
}

 

useThrottle

useThrottle 훅을 사용하면 함수 호출을 스로틀하여 실행 빈도를 제한할 수 있습니다. 이는 크기 조정, 스크롤 또는 양식 입력 변경과 같은 이벤트에 유용할 수 있습니다.

import { useRef } from 'react';

function useThrottle(callback, delay) {
  const lastCall = useRef(0);

  return (...args) => {
    const now = new Date().getTime();

    if (now - lastCall.current >= delay) {
      lastCall.current = now;
      callback(...args);
    }
  };
}

 

사용:

function ThrottledScroll() {
  const handleScroll = useThrottle(() => {
    console.log('Scrolled!');
  }, 200);

  useEffect(() => {
    window.addEventListener('scroll', handleScroll);
    return () => window.removeEventListener('scroll', handleScroll);
  }, [handleScroll]);

  return <div style={{ height: '2000px' }}>Scroll down to see throttled console logs.</div>;
}
반응형
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.