Создаем первый React хук с нуля

Хуки позволяют решать много разных задач в 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>
  );
}

Leave a Comment

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Scroll to Top