
Security News
Axios Supply Chain Attack Reaches OpenAI macOS Signing Pipeline, Forces Certificate Rotation
OpenAI rotated macOS signing certificates after a malicious Axios package reached its CI pipeline in a broader software supply chain attack.
codedsaif-react-hooks
Advanced tools
To make it easy for you to get started with GitLab, here's a list of recommended next steps.
A collection of useful React custom hooks for common functionalities.
npm install codedsaif-react-hooks
A lightweight React hook to detect clicks outside a referenced component. Perfect for dropdowns, modals, and popovers.
/**
* Custom hook that triggers a callback when a user clicks outside the referenced element.
* @param {Function} callback - The function to call when clicking outside the element.
* @returns {React.RefObject} - A ref that should be attached to the element you want to monitor.
* @example
* function Dropdown() {
* const [isOpen, setIsOpen] = useState(false);
* const dropdownRef = useClickOutside(() => setIsOpen(false));
* return (
* <div>
* <button onClick={() => setIsOpen(!isOpen)}>Toggle Menu</button>
* {isOpen && (
* <ul ref={dropdownRef}>
* <li>Option 1</li>
* <li>Option 2</li>
* <li>Option 3</li>
* </ul>
* )}
* </div>
* );
*/
Debounces a value with a specified delay.
/**
* Custom hook to debounce a value after a specified delay.
* @param {*} value - The value to debounce.
* @param {number} [delay=500] - Delay in milliseconds before updating the debounced value.
* @returns {*} - The debounced value.
* @example
* const [searchTerm, setSearchTerm] = useState("");
* const debouncedSearch = useDebounce(searchTerm, 300);
* useEffect(() => {
* if (debouncedSearch) {
* fetch(`https://api.example.com/search?q=${debouncedSearch}`)
* .then((res) => res.json())
* .then((data) => console.log(data));
* }
* }, [debouncedSearch]);
* return (
* <input
* type="text"
* placeholder="Search..."
* onChange={(e) => setSearchTerm(e.target.value)}
* />
* );
*/
Simplifies data fetching with loading and error states.
/**
* Custom hook to fetch data from an API.
* @param {string} url - The URL to fetch data from.
* @param {object} options - Optional fetch options (headers, method, etc.).
* @returns {{ data: any, loading: boolean, error: string|null }} - The fetched data, loading state, and error message.
* @example
* const { data, loading, error } = useFetch("https://api.example.com/users");
* if (loading) return <p>Loading...</p>;
* if (error) return <p>Error: {error}</p>;
* return (
* <ul>
* {data.map(user => (
* <li key={user.id}>{user.name}</li>
* ))}
* </ul>
* );
*/
Persists state in localStorage with automatic JSON parsing/stringifying.
/**
* Custom hook to persist state in localStorage.
* @param {string} key - The localStorage key to store the value under.
* @param {*} initialValue - The initial value to use if none is found in localStorage.
* @returns {[any, function]} - Returns the stored value and a setter function.
* @example
* function Dropdown() {
* const [theme, setTheme] = useLocalStorage("theme", "light");
* useEffect(() => {
* document.body.className = theme;
* }, [theme]);
* return (
* <button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>
* Toggle Theme
* </button>
* );
* }
*/
Tracks the previous value of a state or prop.
/**
* Custom hook to get the previous value of a prop or state.
* @param {*} value - The current value to track.
* @returns {*} - The previous value before the current render.
* @example
* const [count, setCount] = useState(0);
* const prevCount = usePrevious(count);
* return (
* <>
* <p>Current: {count}, Previous: {prevCount}</p>
* <button onClick={() => setCount(count + 1)}>Increase</button>
* </>
* );
*/
Manages theme switching with localStorage persistence.
/**
* Custom hook to manage dark and light theme modes,
* persisting the theme preference in localStorage.
* @returns {[string, function]} - Current theme and a toggle function.
* @example
* const [theme, toggleTheme] = useDarkMode();
* return (
* <button onClick={toggleTheme}>
* Switch to {theme === "light" ? "Dark" : "Light"} Mode
* </button>
* );
*/
Tracks window dimensions with responsive updates.
/**
* Custom hook to get the current window dimensions.
* @returns {{ width: number, height: number }} - The current width and height of the window.
* @example
* function ResponsiveComponent() {
* const { width } = useWindowSize();
* return (
* <div>
* {width > 1024 ? (
* <h1>Desktop View 🖥️</h1>
* ) : width > 768 ? (
* <h1>Tablet View 📱</h1>
* ) : (
* <h1>Mobile View 📲</h1>
* )}
* </div>
* );
*/
Supports both automatic callback fetching and manual slicing with scroll.
/**
* @param {Function|null} callback - Called when scroll reaches bottom (set null for manual mode)
* @param {Object} options - Configuration options
* @param {React.RefObject} [options.containerRef=null] - Scrollable container ref
* @param {boolean} [options.manual=false] - If true, enables manual mode and returns 'limit'
* @param {number} [options.start=10] - Initial limit in manual mode
* @param {number} [options.pace=10] - Increment step for limit
* @param {number} [options.offset=100] - Distance from bottom to trigger scroll (in px)
* @param {boolean} [options.hasMore=true] - Set false to stop infinite loading when no more data
* @param {boolean} [options.debug=false] - Enable funny console logs
*
* @returns {[boolean, Function]|number}
* - If manual=false: returns [isFetching, setIsFetching]
* - If manual=true: returns limit (number)
*
* Example: Auto-fetch Mode (Default)
* -------------------------------------
* const containerRef = useRef(null);
* const [data, setData] = useState([]);
* const [page, setPage] = useState(1);
* const [hasMore, setHasMore] = useState(true);
* const fetchMore = async () => {
* const res = await fetch(`https://medrum.herokuapp.com/feeds/?source=5718e53e7a84fb1901e05971&page=${page}&sort=latest`);
* const json = await res.json();
* if (json.length === 0) setHasMore(false);
* // OR if(json.count === data.length) setHasMore(false);
* else {
* setData(prev => [...prev, ...json]);
* setPage(prev => prev + 1);
* }
* };
* const [isFetching, setIsFetching] = useInfiniteScroll(fetchMore, { containerRef, hasMore });
*
* Example: Manual Slice Mode
* ------------------------------
* const containerRef = useRef(null);
* const [data, setData] = useState([]);
* const [page, setPage] = useState(1);
* const [hasMore, setHasMore] = useState(true);
* const limit = useInfiniteScroll(null, { containerRef, manual: true, start: 10, pace: 10, hasMore });
* const fetchMore = async () => {
* const res = await fetch(`https://medrum.herokuapp.com/feeds/?source=5718e53e7a84fb1901e05971&page=${page}&sort=latest`);
* const json = await res.json();
* if (json.length === 0) setHasMore(false);
* else {
* setData(prev => [...prev, ...json]);
* setPage(prev => prev + 1);
* }
* };
* useEffect(() => {
* fetchMore();
* }, [limit]);
* return (
* <ul ref={containerRef} style={{ height: "400px", overflowY: "scroll" }}>
* {data.slice(0, limit).map(item => (
* <li key={item.id}>{item.title}</li>
* ))}
* </ul>
* );
*/
Runs a callback at a specified interval with support for pausing, debugging, initial delay, and external control.
/**
* @param {Function} callback - Function to run at each interval tick.
* @param {number|null} delay - Interval duration in ms. Use `null` to pause.
* @param {Object} [options={}] - Additional options.
* @param {boolean} [options.debug=false] - Log lifecycle events to the console.
* @param {boolean} [options.stop=false] - Stops interval immediately when set to true.
* @param {boolean} [options.immediate=false] - Fires callback immediately on mount.
* @param {number} [options.initialDelay] - Wait this long before starting interval loop.
* @param {number} [options.maxTicks] - Stop after this many ticks.
*
* @returns {
* count: number,
* reset: () => void,
* forceTick: () => void,
* pause: () => void,
* resume: () => void,
* }
*
* @example
* // Logs "Tick" every second
* useInterval(() => console.log('Tick'), 1000);
*
* @example
* const { count, reset } = useInterval(() => {
* console.log('Tick!');
* }, 1000);
*
* @example
* // With pause/resume capability
* const [delay, setDelay] = useState(1000);
* <button onClick={() => setDelay(prev => prev ? null : 1000)}>Toggle</button>
* useInterval(() => console.log('Toggling...'), delay);
*
* @example
* function CountDown({ initialCount, downLimit, initialDelay = 1000, bgColor = 'transparent' }) {
* const [count, setCount] = useState(initialCount);
* const [delay, setDelay] = useState(null);
* useInterval(() => {
* const newValue = count - 1;
* setCount(newValue)
* if (newValue <= downLimit) setDelay(null);
* }, delay);
* return (
* <div style={{ display: 'flex', backgroundColor: bgColor }}>{count}
* <button onClick={() => setDelay(initialDelay)}>Start</button>
* <button onClick={() => setDelay(null)}>Stop</button>
* </div>
* )
* }
*
* @example
* import React, { useState, useCallback } from 'react';
* import useInterval from './useInterval';
* const ShoppingList = () => {
* // Wait 5 seconds before fetching new data
* const POLL_DELAY = 5000;
* const [items, setItems] = useState([]);
* const fetchItems = useCallback(async () => {
* const response = await fetch('/shopping-list/items');
* const json = await response.json();
* setItems(json);
* }, []);
* useEffect(() => {
* // Fetch items from API on mount
* fetchItems();
* }, []);
* useInterval(() => {
* fetchItems();
* }, POLL_DELAY);
* return (
* <ul>
* {items.map((item) => <li>{item.title}</li>)}
* </ul>
* )
* };
*/
A powerful custom React hook for handling keyboard interactions.
/**
* Features:
* - Detect and respond to individual key presses
* - Match specific target key (e.g. 'a')
* - Maintain history of pressed keys
* - Detect modifier key states: Shift, Alt, Ctrl, Meta
* - Detect multi-key combos like 'ctrl+s', 'cmd+z'
* - Pause and resume key tracking
* - Optionally ignore key presses in input, textarea, or contentEditable fields
*
* @param {Object} options - Configuration options
* @param {Function} [options.onKeyPress] - Function to run when a valid key is pressed
* @param {string} [options.targetKey] - Key to match for triggering `keyMatched` (case-insensitive)
* @param {boolean} [options.enableHistory=true] - Whether to track history of pressed keys
* @param {number} [options.maxHistory=10] - Max number of keys to store in history
* @param {boolean} [options.ignoreInput=false] - If true, ignores key presses from editable elements (input, textarea, contentEditable)
* @param {string[]} [options.combos=[]] - List of keyboard combos to detect (e.g. ['ctrl+s', 'cmd+z'])
*
* @returns {Object} Hook output
* @returns {string|null} keyPressed - The current key being pressed (null if none)
* @returns {boolean} keyMatched - True if the most recent key matched `targetKey`
* @returns {string[]} keyHistory - Array of previously pressed keys (up to `maxHistory`)
* @returns {string|null} comboMatched - The matched combo string, if any (e.g. 'ctrl+s')
* @returns {Object} modifiers - Current modifier key states { shift, alt, ctrl, meta }
* @returns {Function} reset - Clears the keyMatched, keyHistory, and comboMatched states
* @returns {Function} setPaused - Function to pause/resume the hook (use `setPaused(true)` to pause)
*
* @example
* // Basic key detection and match tracking
* const { keyPressed, keyMatched, keyHistory } = useKeyPress({
* onKeyPress: (key) => console.log('You pressed:', key),
* targetKey: 'a',
* enableHistory: true,
* maxHistory: 5,
* });
*
* @example
* // Full usage with modifiers, combo detection, pause and reset
* const {
* keyPressed,
* keyMatched,
* keyHistory,
* comboMatched,
* modifiers,
* reset,
* setPaused,
* } = useKeyPress({
* onKeyPress: (key) => console.log('Key:', key),
* targetKey: 's',
* combos: ['ctrl+s', 'cmd+z'],
* ignoreInput: true,
* });
*
* useEffect(() => {
* if (comboMatched === 'ctrl+s') {
* console.log('Save triggered!');
* }
* }, [comboMatched]);
*/
This package is licensed under the MIT License.
FAQs
To make it easy for you to get started with GitLab, here's a list of recommended next steps.
We found that codedsaif-react-hooks demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
OpenAI rotated macOS signing certificates after a malicious Axios package reached its CI pipeline in a broader software supply chain attack.

Security News
Open source is under attack because of how much value it creates. It has been the foundation of every major software innovation for the last three decades. This is not the time to walk away from it.

Security News
Socket CEO Feross Aboukhadijeh breaks down how North Korea hijacked Axios and what it means for the future of software supply chain security.