Владислав Калачев

Custom Hooks на примерах. Пишем свой useInput, useFetch, useTheme — React Hooks

1 491

Доброго времени суток, друзья. В данной статье мы вновь продолжим серию статей о React hooks. Учитывая количество предыдущих статей о хуках, где были уже рассмотрены все наиболее используемые хуки, вы можете задать мне следующий вопрос: «А можно ли писать свои хуки?» Конечно можно. Данная возможность является одной из самых крутых фич, которую нам дают хуки. Благодаря написанию своих собственных хуков, можно инкапсулировать большие куски кода, использовать внутри другие хуки и при этом пользоваться простотой и гибкостью, которою предоставили нам хуки.

Видео тут:

Исходный код тут

 Как написать свой хук?

Давайте вспомним базовые правила. Любой хук – это функция, имеющая в начале своего наименования «use» и далее включающая название хука, предопределяющего непосредственно его функциональность (например, useState, useEffect и тд). В теле функции описывается логика функции, при этом хук обязательно должен возвращать результаты выполнения в виде значений или функций. С теорией достаточно, давайте перейдем к практике и напишем пару хуков.

Пишем хук useInput 

Для начала поработаем с формами. Представьте, что у нас есть форма с одним полем ввода.

Пример (App.js):


import {useState} from 'react'

function App() {
  const [value, setValue] = useState('')
  
  return (
    <div className="App">
	<form>
      <input 
        value={value} 
        onChange={e => setValue(e.target.value)}
      />
    </div>
 </form>
  );
}

export default App;

Давайте напишем хук useInput, который позволит скрыть всю логику работы с состоянием input и удобно масштабировать ее.

Пример (useInput.js):


import { useState } from "react";

const useInput = (initial, required) => {
  const [value, setValue] = useState(initial);
  const [error, setError] = useState(null);

  return {
    value,
    onBlur: e => {
      if (!e.target.value && required) setError("Required field");
      else setError(null);
    },
    onChange: e => setValue(e.target.value),
    error
  };
};

export default useInput

В данном хуке, помимо возвращаемого значения и функций изменения, на событие onBlur навешена логика обязательного/ необязательного поля с возможностью передачи флага required вторым параметром хука. Ошибка будет храниться отдельным состоянием и ее также можно будет легко кастомизировать. Теперь немного поправим App.js, куда импортируем хук useInput.

Пример:


import useInput from './hooks/useInput'

function App() {
  const name = useInput('hello world', true)

  return (
    <div className="App">
      <form>
      <input {...name}/>
        {name.error && <span style={{ color: 'red'}}>{name.error}</span>}
      </form>
    </div>
  );
}

export default App;

Как видите данный хук прекрасно работает. Если поле ввода будет пустым и не в фокусе, то возникнет предупреждение, что данное поле обязательно должно быть заполнено. 

Пишем хук useFetch

Данный хук предназначен для взаимодействия с сервером путем отправления запросов и возвращения нужных ответов для дальнейшего рендеринга. Давайте посмотрим на код хука

Пример (useFetch.js):


import { useState, useEffect } from "react";

const useFetch = (url, options) => {

  const [status, setStatus] = useState({
    loading: false,
    data: undefined,
    error: undefined
  });

  function fetchNow(url, options) {
    setStatus({ loading: true });
    fetch(url, options)
      .then((res) => res.json())
      .then((res) => {
        setStatus({ loading: false, data: res.data });
      })
      .catch((error) => {
        setStatus({ loading: false, error });
      });
  }

  useEffect(() => {
    if (url) {
      fetchNow(url, options);
    }
  }, []);

  return { ...status, fetchNow };
}

export default useFetch

Хук принимает в себя два параметра, первый из которых «url», куда будет происходить вызов запроса, и «options», в который можно передать дополнительные параметры стандартной функции fetch.

Хук возвращает функцию fetchNow и все состояния, которые он хранит в объекте status.

Пример (App.js):


import useInput from './hooks/useInput'
import useFetch from './hooks/useFetch'

function App() {

  const name = useInput('hello wordl', true)

  const { data, loading, error } = useFetch('https://www.reddit.com/r/news.json')

  if(loading) return 'Loading ...'

  if(error) {
    console.log("error", error);
    return null;
  }

  return (
    <div className="App">
      <form>
      <input {...name}/>
        {name.error && <span style={{ color: 'red'}}>{name.error}</span>}
      </form>
      {JSON.stringify(data && data.dist)}
    </div>
  );
}

export default App;

Как видите, для демонстрации работы хука, в useFecth был передан url сервера www.reddit.com. Данный хук выполняется сразу после рендеринга компонента, этот вызов можно ограничить и заменить на вызов по событию (клик кнопки, скрол и тд.), используя функции fetchNow.

Пишем хук useTheme 

И последний кастомный хук, который мы рассмотрим в данной статье является useTheme, основная задача которого – изменение цвета приложения.

Пример (useTheme.js):


const useTheme = () => {
    const [theme, setTheme] = useState("light");
    const toggleTheme = () => {
        if (theme !== "dark") {
            localStorage.setItem("theme", "dark");
            setTheme("dark");
        } else {
            localStorage.setItem("theme", "light");
            setTheme("light");
        }
    };

    useEffect(() => {
        const localTheme = localStorage.getItem("theme");
        if (localTheme) {
            setTheme(localTheme);
        }
    }, []);

    return {
        theme,
        toggleTheme
    };
};

export default useTheme

Хук хранит первоначальное состояние в localStorage и затем помещает его в useState. Если тема отсутствует в localStorage, то берётся дефолтное значение «light»

Пример (App.js):


import useInput from './hooks/useInput'
import useFetch from './hooks/useFetch'
import useTheme from './hooks/useTheme'
import './App.css'

function App() {

  const name = useInput('hello wordl', true)

  const { data, loading, error } = useFetch('https://www.reddit.com/r/news.json')

  const { theme, toggleTheme } = useTheme();

  if(loading) return 'Loading ...'

  if(error) {
    console.log("error", error);
    return null;
  }

  return (
    <div className={`App ${theme}`}>
      <button type="button" onClick={toggleTheme}>
          Switch theme
      </button>
      <form>
      <input {...name}/>
        {name.error && <span style={{ color: 'red'}}>{name.error}</span>}
      </form>
      {JSON.stringify(data && data.dist)}
    </div>
  );
}

export default App;

Чтобы данный хук заработал, не забудьте добавить стили для оформления темы.


.App.dark {
  background-color: #000;
}

.App-logo {
  height: 40vmin;
  pointer-events: none;
}

Заключение

Сегодня мы рассмотрели понятие Custom Hooks в React, поговорили о теории хуков, а также научились создавать свои хуки (useInput, useFetch, useTheme). Надеюсь, что данный материал был вам полезен. Учитесь, думайте, пишите код. Удачного кодинга, друзья!

Подписывайтесь на наш канал в Telegram и на YouTube для получения самой последней и актуальной информации. 

Комментарии:

Добавить комментарий

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