Урок 12

Практика: набор полезных хуков

Собрали набор хуков, которые пригодятся в реальных проектах.

usePrevious

Хранит предыдущее значение:

function usePrevious<T>(value: T): T | undefined {
    const ref = useRef<T>();

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

    return ref.current;
}

useInterval

Управляемый интервал:

function useInterval(callback: () => void, delay: number | null) {
    const savedCallback = useRef(callback);

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

    useEffect(() => {
        if (delay === null) return;

        const id = setInterval(() => savedCallback.current(), delay);
        return () => clearInterval(id);
    }, [delay]);
}

useWindowSize

Размер окна:

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

    useEffect(() => {
        function handle() {
            setSize({
                width: window.innerWidth,
                height: window.innerHeight,
            });
        }

        window.addEventListener('resize', handle);
        return () => window.removeEventListener('resize', handle);
    }, []);

    return size;
}

useOnlineStatus

Онлайн/оффлайн:

function useOnlineStatus() {
    const [online, setOnline] = useState(navigator.onLine);

    useEffect(() => {
        const on = () => setOnline(true);
        const off = () => setOnline(false);

        window.addEventListener('online', on);
        window.addEventListener('offline', off);

        return () => {
            window.removeEventListener('online', on);
            window.removeEventListener('offline', off);
        };
    }, []);

    return online;
}

useForm

Мини-форма:

function useForm<T extends Record<string, unknown>>(initial: T) {
    const [values, setValues] = useState(initial);

    function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
        const { name, value, type, checked } = e.target;
        setValues((v) => ({
            ...v,
            [name]: type === 'checkbox' ? checked : value,
        }));
    }

    function reset() {
        setValues(initial);
    }

    return { values, handleChange, reset, setValues };
}

useKeyPress

Отслеживает нажатие клавиши:

function useKeyPress(targetKey: string): boolean {
    const [pressed, setPressed] = useState(false);

    useEffect(() => {
        function down(e: KeyboardEvent) {
            if (e.key === targetKey) setPressed(true);
        }
        function up(e: KeyboardEvent) {
            if (e.key === targetKey) setPressed(false);
        }

        window.addEventListener('keydown', down);
        window.addEventListener('keyup', up);

        return () => {
            window.removeEventListener('keydown', down);
            window.removeEventListener('keyup', up);
        };
    }, [targetKey]);

    return pressed;
}

useCopyToClipboard

Копирование в буфер:

function useCopyToClipboard() {
    const [copied, setCopied] = useState(false);

    async function copy(text: string) {
        await navigator.clipboard.writeText(text);
        setCopied(true);
        setTimeout(() => setCopied(false), 1500);
    }

    return { copied, copy };
}

useIntersectionObserver

Отслеживает появление в viewport:

function useIntersectionObserver<T extends HTMLElement>() {
    const ref = useRef<T>(null);
    const [visible, setVisible] = useState(false);

    useEffect(() => {
        if (!ref.current) return;

        const observer = new IntersectionObserver(([entry]) => {
            setVisible(entry.isIntersecting);
        });

        observer.observe(ref.current);
        return () => observer.disconnect();
    }, []);

    return { ref, visible };
}

Как проектировать свои хуки

  1. Начни с логики в компоненте
  2. Если логика повторяется — вынеси в хук
  3. Название начинается с use
  4. Хук возвращает то, что нужно компоненту
  5. Не бойся возвращать объект или массив
  6. Держи хук сфокусированным на одной задаче

Итоги

  • Кастомные хуки — стандарт современного React
  • Много готовых хуков можно найти в библиотеках (react-use, usehooks-ts)
  • Свои хуки лучше держать маленькими
  • Название с use обязательно
  • Хук должен быть переиспользуемым