[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>;
}
'Front-end' 카테고리의 다른 글
[TypeScript] enum vs. const (0) | 2024.11.22 |
---|---|
axios vs. fetch (0) | 2024.11.21 |
[React] Tanstack Query(구 React-Query)에서 query key 관리하는 라이브러리(query-key-factory) (0) | 2024.11.16 |
[React] Zustand Deep DIVE 하기 (2) | 2024.11.15 |
[Next.js] Sentry로 효율적인 에러 모니터링하는 법(자동화 에러 추적 도구) (2) | 2024.11.14 |
소중한 공감 감사합니다