UNPKG

codedsaif-react-hooks

Version:

To make it easy for you to get started with GitLab, here's a list of recommended next steps.

400 lines (367 loc) 13.3 kB
# React Hooks A collection of useful React custom hooks for common functionalities. ## Installation ```bash npm install codedsaif-react-hooks ``` ## Available Hooks ### useClickOutside A lightweight React hook to detect clicks outside a referenced component. Perfect for dropdowns, modals, and popovers. ```jsx /** * 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> * ); */ ``` ### useDebounce Debounces a value with a specified delay. ```jsx /** * 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)} * /> * ); */ ``` ### useFetch Simplifies data fetching with loading and error states. ```jsx /** * 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> * ); */ ``` ### useLocalStorage Persists state in localStorage with automatic JSON parsing/stringifying. ```jsx /** * 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> * ); * } */ ``` ### usePrevious Tracks the previous value of a state or prop. ```jsx /** * 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> * </> * ); */ ``` ### useTheme Manages theme switching with localStorage persistence. ```jsx /** * 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> * ); */ ``` ### useWindowSize Tracks window dimensions with responsive updates. ```jsx /** * 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> * ); */ ``` ### useInfiniteScroll Supports both automatic callback fetching and manual slicing with scroll. ```jsx /** * @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> * ); */ ``` ### useInterval Runs a callback at a specified interval with support for pausing, debugging, initial delay, and external control. ```jsx /** * @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> * ) * }; */ ``` ### useKeyPress A powerful custom React hook for handling keyboard interactions. ```jsx /** * 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]); */ ``` ## License This package is licensed under the [MIT License](https://opensource.org/licenses/MIT).