Сборник пользовательских хуков на React.js
Хук для взаимодействия с клавиатурой
import { useEffect } from 'react'
const useKeyListener = (callback) => {
useEffect(() => {
const listener = (e) => {
e = e || window.event
const tagName = e.target.localName || e.target.tagName
// Принимаем только события на уровне body,
// чтобы избежать их перехвата, например, в полях ввода
if (tagName.toUpperCase() === 'BODY') {
callback(e)
}
}
document.addEventListener('keydown', listener, true)
return () => {
document.removeEventListener('keydown', listener, true)
}
}, [callback])
}
export default useKeyListener
Хук для склонения слов
Хук, который в зависимости от числа подставляет необходимое склонение слова или выражения.
Пример использования:
const { text } = useWordDeclination(correctAnswersCount, [
"верный ответ",
"верных ответа",
"верных ответов",
])
Реализация (TypeScript):
import { useMemo } from "react"
export const useWordDeclination = (
n: number,
strings: [string, string, string],
) => {
const text = useMemo(() => {
const words = [strings[0], strings[1], strings[2]]
return words[
n % 100 > 4 && n % 100 < 20
? 2
: [2, 0, 1, 1, 1, 2][n % 10 < 5 ? n % 10 : 5]
]
}, [n, strings])
return { text }
}
Хук для изменения мета-тегов
Хук, изменяющий title и description мета-тэги для страницы при её открытии и восстанавливающий их значения при ее покидании.
Пример использования:
usePageMeta({ title, description })
Реализация:
import { useEffect } from "react"
type Props = {
title: string
description?: string
}
const DEFAULT = {
title: "Заголовок страницы по умолчанию (заголовок для главной страницы)",
description: "Описание по умолчанию (описание для главной страницы)",
}
export const usePageMeta = ({ title, description }: Props) => {
useEffect(() => {
document.title = title
description &&
document
.querySelector('head meta[name="description"]')
?.setAttribute("content", description)
return () => {
document.title = DEFAULT.title
document
.querySelector('head meta[name="description"]')
?.setAttribute("content", DEFAULT.description)
}
}, [title, description])
}
Хук для управления модальным окном
export function useModal() {
const [isOpened, setIsOpened] = useState(false);
const restrictBodyScroll = () => {
document.body.style.height = "100vh";
document.body.style.overflow = "hidden";
};
const allowBodyScroll = () => {
document.body.style.height = "";
document.body.style.overflow = "";
};
const open = () => {
setTimeout(() => {
restrictBodyScroll();
}, 0);
setIsOpened(true);
};
const close = () => {
allowBodyScroll();
setIsOpened(false);
};
useEffect(() => {
return close;
}, []);
return {
isOpened,
open,
close,
};
}
setTimeout
в open
нужен для того, чтобы скролл body
корректно запретился в том случае, если модалка открывается сразу после закрытия предыдущей.
Пример использования:
const SomeComponent = () => {
const confirmModal = useModal();
return (
<div>
<button onClick={confirmModal.open}>Открыть модальное окно</button>
<SomeModalComponent
isOpened={confirmModal.isOpened}
close={confirmModal.close}
/>
</div>
);
};
export default SomeComponent;
Хук для получения размеров окна
import { useState, useEffect } from "react";
interface WindowSize {
width: number;
height: number;
}
const useWindowSize = (): WindowSize => {
const [windowSize, setWindowSize] = useState<WindowSize>({
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;
};
Хук для setInterval
import { useState, useEffect, useRef } from "react";
const useInterval = (callback: () => void, delay: number | null) => {
const savedCallback = useRef<() => void>();
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
useEffect(() => {
function tick() {
savedCallback.current && savedCallback.current();
}
if (delay !== null && delay > 0) {
let id = setInterval(tick, delay);
return () => clearInterval(id);
} else {
tick();
}
}, [delay]);
};
Хук для доступа к предыдущему значению стейта
import { useRef, useEffect } from "react";
const usePrevious = <T>(value: T): T | undefined => {
const ref = useRef<T>();
useEffect(() => {
ref.current = value;
});
return ref.current;
};
export default usePrevious;
Пример использования:
import React, { useState } from "react";
import usePrevious from "./usePrevious";
const Counter = () => {
const [count, setCount] = useState(0);
const prevCount = usePrevious(count);
const handleClick = () => {
setCount(count => count + 1);
};
return (
<div>
Current count: {count}, Previous count: {prevCount}
<button onClick={handleClick}>Increment</button>
</div>
);
};
export default Counter;