Кастомный React хук— useDebounce
Tags: javascript, react, unsplash, api
Published: 2023-01-16 18:59:13

Одна из замечательных особенностей React заключается в том, что он позволяет создавать собственные хуки и предлагать более эффективные способы решения проблем. Понимание того, как создать собственный хук, является ценным навыком разработчика. Есть много отличных кастомных хуков, которые «стандартизированы» в сообществе разработчиков React, и `useDebounce()` — один из них.

В этой статье мы собираемся создать мини-приложение, которое позволит пользователям искать изображения из [Unsplash API](https://unsplash.com/developers). Я также предоставлю ссылку на [репозиторий Github](https://github.com/MukhammadSobirov/useDebounce_demo).

## Создание акаунта в Unsplash

1. Сначала нам нужно зарегистрироваться в [Unsplash](https://unsplash.com/join)
2. Далее нам нужно создать [там новое приложение](https://unsplash.com/oauth/applications)


![создание акканта в unsplash](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d8p1bort5ekwk4de7kig.png)

важно отметить, что мы будем использовать их демонстрационное приложение, которое имеет ограничение в 50 запросов в час. Для готовых к производству приложений проверьте их требования на той же странице. Также ознакомьтесь с их [рекомендациями по использованию API.](https://unsplash.com/oauth/applications/new)

3. Наконец, обязательно скопируйте ключ доступа и секретные ключи, которые они предоставляют.

## Создание приложения

Просто создайте минимальное React приложение. Я использую [Vite](https://vitejs.dev/).

```bash
npm create vite@latest
```

Выберите простой шаблон React на основе JavaScript и позвольте ему настроить проект. После завершения настройки нажмите `npm i`.

Когда все будет готово, создайте файл .env в корневой папке проекта.


![создание env файла](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pki4gog5pd6vw1qmummy.png)

И добавьте ключи доступа и секретные ключи, как показано ниже. Если вы используете Vite, важно начинать имя ключа с «VITE».

```bash
VITE_ACCESS_KEY="your key"
VITE_SECRET_KEY="your key"
```

Затем перейдите к файлу App.jsx и удалите оттуда лишний код, как показано ниже:

```jsx
import { useState } from "react";
import "./App.css";

function App() {
  return <div className="App"></div>;
}

export default App;
```

Мы собираемся добавить сюда простой компонент search, чтобы пользователь мог ввести слово для поиска.

```jsx
import { useState } from "react";
import "./App.css";

function App() {
  const [searchTerm, setSearchTerm] = useState("");

  return (
    <div className="App">
      <div>
        <label htmlFor="search-input">Search Unsplash images</label>
        <input type="search" id="search-input" value={searchTerm} onChange={(e) => setSearchTerm(e.target.value)} />
      </div>
    </div>
  );
}

export default App;
```

Идея в том, что мы отправляем запрос в Unsplash API каждый раз, когда пользователь обновляет слово поиска. Простым способом реализации этой логики было бы использование useEffect с searchTerm в массиве зависимостей.

```jsx
import { useState, useEffect } from "react";
import "./App.css";

function App() {
  const [searchTerm, setSearchTerm] = useState("");

  const handleSearch = () => {
    console.log("searching for", searchTerm);
  };

  useEffect(() => {
    handleSearch();
  }, [searchTerm]);

  return (
    <div className="App">
      <div>
        <label htmlFor="search-input">Search Unsplash images</label>
        <input type="search" id="search-input" value={searchTerm} onChange={(e) => setSearchTerm(e.target.value)} />
      </div>
    </div>
  );
}

export default App;
```

Можете ли вы угадать проблему с этим подходом? Откроем консоль браузера и проверим, что там происходит:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yswg3xnxe8sf1p1bzfh5.gif)

Помните, что у нас есть ограничение в 50 запросов в час. Даже если бы у нас было больше, отправка запроса в API при каждом вводе пользователем — не очень масштабируемая и дорогая операция. В идеале вы хотели бы дождаться, пока пользователь введет слово целиком, а затем отправить запрос. Вот где в игру вступает useDebounce! так что давайте реализуем это дальше.

### useDebounce()

Давайте создадим файл с именем useDebounce:


![useDebounce хук](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/11isdynzjfg9c3klwd50.png)

Внутри файла мы собираемся написать сам хук. Идея в том, что мы хотим предотвратить быстрое изменение состояния `searchTerm` . Этот хук принимает два входных параметра — значение, которое должно “отскочить”, и задержка в миллисекундах (обычно 500). При объединении `useDebounce` с `useEffect` вы можете быть уверены, что будет отражено только самое последнее значение, поскольку хук не будет вызываться в течение указанного времени. Вот код:

```jsx
function useDebounce(value, delay) {
  // value and delay in ms (1000ms = 1s)
  // debounced values
  const [debouncedValue, setDebouncedValue] = useState(value);

  useEffect(
    () => {
      // Update debounced value after delay
      const t = setTimeout(() => {
        setDebouncedValue(value);
      }, delay);

      // clean up the timeout after value changes
      return () => {
        clearTimeout(t);
      };
    },
    [value, delay] // re-run if value or delay changes
  );
  return debouncedValue;
}

export default useDebounce;
```

Наконец, давайте объединим это с нашей существующей логикой и сравним с предыдущим поведением:

```jsx
import { useState, useEffect } from "react";
import "./App.css";
import useDebounce from "./useDebounce";

function App() {
  const [searchTerm, setSearchTerm] = useState("");
  const debouncedSearchTerm = useDebounce(searchTerm, 500);

  const handleSearch = () => {
    console.log("searching for", searchTerm);
  };

  useEffect(() => {
    if (debouncedSearchTerm) {
      handleSearch();
    }
  }, [debouncedSearchTerm]);

  return (
    <div className="App">
      <div>
        <label htmlFor="search-input">Search Unsplash images</label>
        <input type="search" id="search-input" value={searchTerm} onChange={(e) => setSearchTerm(e.target.value)} />
      </div>
    </div>
  );
}

export default App;
```

Разве это не безумие, насколько эффективной стала наша поисковая логика?


![демонстрация](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o2vmw96cxnh0yhb84ndz.gif)

### Unsplash интеграция

Теперь с этим кодом мы можем безопасно интегрировать поиск Unsplash.

```jsx
import { useState, useEffect } from "react";
import "./App.css";
import useDebounce from "./useDebounce";

const API_URL = "https://api.unsplash.com/search/photos";

function App() {
  const [searchTerm, setSearchTerm] = useState("");
  const [results, setResults] = useState([]);

  const debouncedSearchTerm = useDebounce(searchTerm, 500);

  const handleSearch = async () => {
    const response = await fetch(
      `${API_URL}?query=${debouncedSearchTerm}&client_id=${import.meta.env.VITE_ACCESS_KEY}`
    );
    const data = await response.json();
    return data.results;
  };

  useEffect(() => {
    if (debouncedSearchTerm) {
      handleSearch().then((results) => setResults(results));
    }
  }, [debouncedSearchTerm]);

  return (
    <div className="App">
      <div>
        <label htmlFor="search-input">Search Unsplash images</label>
        <input type="search" id="search-input" value={searchTerm} onChange={(e) => setSearchTerm(e.target.value)} />
      </div>
      <div>
        {results?.map((result) => (
          <img key={result.id} src={result.urls.small} alt={result.alt_description} />
        ))}
      </div>
    </div>
  );
}

export default App;
```


![финальное приложение](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hi1rtlaq1pa8u4l06eth.gif)

### Вот и все

Я надеюсь, что вы нашли эту статью/туториал полезным. Я рекомендую вам попробовать `useDebounce` в своих проектах и создавать высококачественные приложения. Вот ссылка на [репозиторий Github](https://github.com/MukhammadSobirov/useDebounce_demo).