Хуки позволяют решать много разных задач в React-проектах. Подробнее о философии хуков можно почитать в официальной документации.
В этой статье мы создадим наш собственный кастомный хук, который будет позволять пользователям копировать сниппеты кода или любой другой текст в нашем приложении.
Что мы хотим сделать?
На сайте reedbarger.com используется пакет react-copy-to-clipboard
, который позволяет пользователям копировать код из статей.
Пользователь наводит курсор на сниппет кода, кликает по кнопке, которая появляется по hover-y и код копируется в буфер обмена. Это позволяет быстро вставлять и использовать код где угодно.

Как создать свой react-copy-to-clipboard
Вместо того, чтобы использовать стороннюю библиотеку, давайте воссоздадим эту функциональность с помощью своего кастомного React-хука.
Как и любой другой кастомный хук, мы положим его в отдельную папку – utils
или lib
. Это папка будет предназначено для функций, которые можно переиспользовать во всем приложении.
Создадим файл useCopyToClipboard.js
и одноименную функцию. Импортируем React.
Есть много разных способов с помощью которых можно копировать текст в буфер обмена. Мы будем использовать библиотеку copy-to-clipboard
. Она возвращает функцию, которую мы назовем copy.
// utils/useCopyToClipboard.js import React from "react"; import copy from "copy-to-clipboard"; export default function useCopyToClipboard() {}
Дальше мы создадим функцию, которая будет использоваться везде, где нужно скопировать текст. Назовем ее handleCopy
.
Создаем handleCopy
Во-первых, убедимся, что функция принимает только строки или числа. Для этого создадим if-else
, который будет выводить ошибку в консоль, если на вход будут приходить данные не того типа.
import React from "react"; import copy from "copy-to-clipboard"; export default function useCopyToClipboard() { const [isCopied, setCopied] = React.useState(false); function handleCopy(text) { if (typeof text === "string" || typeof text == "number") { // copy } else { // don't copy console.error( `Cannot copy typeof ${typeof text} to clipboard, must be a string or number.` ); } } }
Дальше возьмем входные данные и конвертируем их в строку, которую потом будем передавать в функцию copy
. Из хука будем возвращать функцию handleCopy
, которая и будет использоваться в нашем приложении. Она будет вызываться по клику на кнопку.
import React from "react"; import copy from "copy-to-clipboard"; export default function useCopyToClipboard() { function handleCopy(text) { if (typeof text === "string" || typeof text == "number") { copy(text.toString()); } else { console.error( `Cannot copy typeof ${typeof text} to clipboard, must be a string or number.` ); } } return handleCopy; }
Также нам нужен state, который будет показывать, был скопирован текст или нет. Будем использовать useState
хук и создадим переменную isCopied
и setter setCopy
.
Начальное значение isCopied
будет false
. Если текст будет успешно скопирован, isCopied
будет меняться на true
.
Мы будем возвращать isCopied из нашего хука вместе с функцией handleCopy
.
import React from "react"; import copy from "copy-to-clipboard"; export default function useCopyToClipboard(resetInterval = null) { const [isCopied, setCopied] = React.useState(false); function handleCopy(text) { if (typeof text === "string" || typeof text == "number") { copy(text.toString()); setCopied(true); } else { setCopied(false); console.error( `Cannot copy typeof ${typeof text} to clipboard, must be a string or number.` ); } } return [isCopied, handleCopy]; }
Как использовать useCopyToClipboard
После того, как хук создан, мы можем использовать его в любом компоненте нашего приложения.
Давайте вызовем функцию handleCopy
в компоненте кнопки. Все, что для этого нужно – вызвать handleCopy
по клику на кнопку. Также мы будем менять иконку на кнопке, если текст скопирован успешно.
import React from "react"; import ClipboardIcon from "../svg/ClipboardIcon"; import SuccessIcon from "../svg/SuccessIcon"; import useCopyToClipboard from "../utils/useCopyToClipboard"; function CopyButton({ code }) { const [isCopied, handleCopy] = useCopyToClipboard(); return ( <button onClick={() => handleCopy(code)}> {isCopied ? <SuccessIcon /> : <ClipboardIcon />} </button> ); }
Меняем иконку на кнопке
Еще одно улучшение, которое нужно сделать – через некоторое время после копирования вернуть иконку кнопки копирования в исходное состояние. На данный момент после копирования текста значение isCopied
всегда будет true
, поэтому иконка на кнопке копирования не будет возвращаться в исходное состояние.

Мы будем сбрасывать состояние через несколько секунд. В хуке useCopyToClipboard
мы добавим параметр resetInterval
(значение по умолчанию – null
). Таким образом, если параметр не будет передан, состояние не будет сбрасываться.
Добавим useEffect
, в котором, если в хук передан параметр resetInterval
и текст был скопирован, будем возвращать состояние isCopied
в исходное.
import React from "react"; import copy from "copy-to-clipboard"; export default function useCopyToClipboard(resetInterval = null) { const [isCopied, setCopied] = React.useState(false); const handleCopy = React.useCallback((text) => { if (typeof text === "string" || typeof text == "number") { copy(text.toString()); setCopied(true); } else { setCopied(false); console.error( `Cannot copy typeof ${typeof text} to clipboard, must be a string or number.` ); } }, []); React.useEffect(() => { let timeout; if (isCopied && resetInterval) { timeout = setTimeout(() => setCopied(false), resetInterval); } return () => { clearTimeout(timeout); }; }, [isCopied, resetInterval]); return [isCopied, handleCopy]; }
В заключение, обернем handleCopy в useCallback хук, чтобы функция гарантированно не создавалась по новой во время каждого рендера.
Заключение
Мы создали свой собственный хук, который позволяет копировать текст и сбрасывает состояние через заданный промежуток времени. Код с примером использования и результат можно посмотреть ниже:
import React from "react"; import ClipboardIcon from "../svg/ClipboardIcon"; import SuccessIcon from "../svg/SuccessIcon"; import useCopyToClipboard from "../utils/useCopyToClipboard"; function CopyButton({ code }) { // isCopied is reset after 3 second timeout const [isCopied, handleCopy] = useCopyToClipboard(3000); return ( <button onClick={() => handleCopy(code)}> {isCopied ? <SuccessIcon /> : <ClipboardIcon />} </button> ); }
