import { useCallback, useEffect, useReducer, useRef, useState } from 'react'; import ResizeObserver from 'resize-observer-polyfill'; export function usePrevious(value: any) { const ref = useRef(); useEffect(() => void (ref.current = value), [value]); return ref.current; } export function useMeasure() { const ref = useRef(); const [bounds, set] = useState({ left: 0, top: 0, width: 0, height: 0 }); const [ro] = useState(() => new ResizeObserver(([entry]) => set(entry.contentRect))); useEffect(() => { if (ref.current) ro.observe(ref.current as any); return () => ro.disconnect(); }, []); return [{ ref }, bounds]; } export function useStore(initialState: T) { return useReducer((prev: T, payload: Partial) => ({ ...prev, ...payload }), initialState); } export function useLockBodyScroll(lock: boolean) { useEffect(() => { if (!lock) { return; } // Get original body overflow const originalStyle = window.getComputedStyle(document.body).overflow; // Prevent scrolling on mount document.body.style.overflow = 'hidden'; // Re-enable scrolling when component unmounts return () => void (document.body.style.overflow = originalStyle); }, [lock]); // Empty array ensures effect is only run on mount and unmount } export function useKeyDown(targetKey: string) { // State for keeping track of whether key is pressed const [keyPressed, setKeyPressed] = useState(false); // If pressed key is our target key then set to true function downHandler({ key }: any) { if (key === targetKey) { arguments[0].stopPropagation(); setKeyPressed(true); } } // If released key is our target key then set to false function upHandler({ key }: any) { if (key === targetKey) { arguments[0].stopPropagation(); setKeyPressed(false); } } // Add event listeners useEffect(() => { window.addEventListener('keydown', downHandler); window.addEventListener('keyup', upHandler); // Remove event listeners on cleanup return () => { window.removeEventListener('keydown', downHandler); window.removeEventListener('keyup', upHandler); }; }, []); // Empty array ensures that effect is only run on mount and unmount return keyPressed; } export function useClickOutSide( target: React.RefObject, callback: (e: HTMLElement | null) => void, pre = false ) { const clickOutside = useCallback( e => { if (!target.current) { return; } const current = pre ? target.current.previousElementSibling : target.current; if (current && !current.contains(e.target)) { callback(e.target); } }, [target.current, callback, pre] ); useEffect(() => { document.addEventListener('click', clickOutside, true); return () => { document.removeEventListener('click', clickOutside, true); }; }, [target.current, callback, pre]); } export function useWhyDidYouUpdate(name: string, props: any) { // Get a mutable ref object where we can store props ... // ... for comparison next time this hook runs. const previousProps = useRef(null); useEffect(() => { if (previousProps.current) { // Get all keys from previous and current props const allKeys = Object.keys({ ...previousProps.current, ...props }); // Use this object to keep track of changed props const changesObj = {} as any; // Iterate through keys allKeys.forEach(key => { // If previous is different from current if (previousProps.current[key] !== props[key]) { // Add to changesObj changesObj[key] = { from: previousProps.current[key], to: props[key], }; } }); // If changesObj not empty then output to console if (Object.keys(changesObj).length) { console.log('[why-did-you-update]', name, changesObj); } } // Finally update previousProps with current props for next hook call previousProps.current = props; }); }