5 React-хуков, которые пригодятся в любом проекте

от автора

Хочу рассказать о пяти простых React-хуках, которые пригодятся в любом проекте. Причём, полезность этих хуков не зависит от того, в каком именно приложении их будут использовать. Описывая каждый из них, я рассказываю о его реализации и привожу пример его использования в клиентском коде.


Хук useModalState

Модальные окна широко используются в веб-приложениях, они применяются в самых разных ситуациях. При работе с подобными окнами быстро приходит понимание того, что управление их состоянием — это трудоёмкая задача, решая которую, приходится постоянно выполнять одни и те же рутинные действия. А если имеется код, написание которого отнимает немало сил и времени, код, который приходится постоянно повторять в разных местах приложения, это значит, что такой код имеет смысл абстрагировать, оформив в виде самостоятельной сущности. Хук useModalState — это именно такой код, используемый для управления состоянием модальных окон.

Собственные версии этого хука предоставляют многие библиотеки. Одна из них — это Chakra UI. Если вас интересуют подробности об этой библиотеке — вот мой материал о ней.

Реализация useModalState весьма проста, даже тривиальна. Но опыт подсказывает мне, что гораздо лучше пользоваться им, чем постоянно заново писать код для управления состоянием модальных окон.

Вот код этого хука:

import React from "react"; import Modal from "./Modal";  export const useModalState = ({ initialOpen = false } = {}) => {   const [isOpen, setIsOpen] = useState(initialOpen);    const onOpen = () => {     setIsOpen(true);   };    const onClose = () => {     setIsOpen(false);   };    const onToggle = () => {     setIsOpen(!isOpen);   };    return { onOpen, onClose, isOpen, onToggle }; }; 

А вот — пример его использования:

const Client = () => {   const { isOpen, onToggle } = useModalState();    const handleClick = () => {     onToggle();   };    return (     <div>       <button onClick={handleClick} />       <Modal open={isOpen} />     </div>   ); };  export default Client; 

Хук useConfirmationDialog

Хук useConfirmationDialog тоже имеет отношение к модальным окнам. И им я тоже пользуюсь довольно часто. Если пользователь некоего приложения выполняет какие-то важные действия, вроде удаления чего-либо, у него принято запрашивать подтверждение выполнения подобных действий. Поэтому такую логику имеет смысл абстрагировать в виде хука. Вот — один из вариантов реализации хука useConfirmationDialog:

import React, { useCallback, useState } from 'react'; import ConfirmationDialog from 'components/global/ConfirmationDialog';  export default function useConfirmationDialog({     headerText,     bodyText,     confirmationButtonText,     onConfirmClick, }) {     const [isOpen, setIsOpen] = useState(false);      const onOpen = () => {         setIsOpen(true);     };      const Dialog = useCallback(         () => (             <ConfirmationDialog                 headerText={headerText}                 bodyText={bodyText}                 isOpen={isOpen}                 onConfirmClick={onConfirmClick}                 onCancelClick={() => setIsOpen(false)}                 confirmationButtonText={confirmationButtonText}             />         ),         [isOpen]     );      return {         Dialog,         onOpen,     }; } 

Вот — пример его использования:

import React from "react"; import { useConfirmationDialog } from './useConfirmationDialog'  function Client() {   const { Dialog, onOpen } = useConfirmationDialog({     headerText: "Delete this record?",     bodyText:       "Are you sure you want delete this record? This cannot be undone.",     confirmationButtonText: "Delete",     onConfirmClick: handleDeleteConfirm,   });    function handleDeleteConfirm() {     //TODO: удалить   }    const handleDeleteClick = () => {     onOpen();   };    return (     <div>       <Dialog />       <button onClick={handleDeleteClick} />     </div>   ); }  export default Client; 

Тут стоит обратить внимание на то, что эта реализация useConfirmationDialog нормально работает до тех пор, пока в модальном окне подтверждения операции нет управляемых элементов, представленных полями для ввода данных. Если в вашем окне такие элементы имеются — лучше создать для такого модального окна отдельный компонент. Дело тут в том, что вам вряд ли понравится то, что содержимое модального окна, включая и такие поля, будет повторно рендериться каждый раз, когда пользователь вводит в поля какие-то данные.

Хук useAsync

Грамотная поддержка асинхронных операций в приложении — это задача, решить которую сложнее, чем кажется на первый взгляд. Так, может иметься множество переменных, хранящихся в состоянии, за которыми нужно наблюдать в процессе выполнения подобных операций. Приложение может сообщать пользователю о ходе выполнения асинхронной операции, показывая ему индикатор прогресса. Кроме того, нужно обрабатывать ошибки асинхронных операций и, если они происходят, выдавать адекватные сообщения о них. В результате наличие в React-проекте хорошо проработанного фреймворка, обеспечивающего поддержку асинхронных операций, окупится сторицей. Хук useAsync может оказаться полезным именно для решения вышеописанных задач. Вот его код:

export const useAsync = ({ asyncFunction }) => {   const [loading, setLoading] = useState(false);   const [error, setError] = useState(null);   const [result, setResult] = useState(null);    const execute = useCallback(     async (...params) => {       try {         setLoading(true);         const response = await asyncFunction(...params);         setResult(response);       } catch (e) {         setError(e);       }       setLoading(false);     },     [asyncFunction]   );    return { error, result, loading, execute }; }; 

А ниже показан пример его использования:

import React from "react";  export default function Client() {   const { loading, result, error, execute } = useAsync({     asyncFunction: someAsyncTask,   });    async function someAsyncTask() {     // выполнение асинхронной операции   }    const handleClick = () => {     execute();   };    return (     <div>       {loading && <p>loading</p>}       {!loading && result && <p>{result}</p>}       {!loading && error?.message && <p>{error?.message}</p>}       <button onClick={handleClick} />     </div>   ); } 

Такой хук несложно написать самостоятельно. Я часто поступаю именно так. Но вам, возможно, имеет смысл присмотреться к более зрелой библиотечной реализации подобного механизма. Например — к этой.

Хук useTrackErrors

Валидация форм — это ещё одна задача, решаемая в рамках React-приложений, которую программисты находят достаточно скучной. Учитывая это, можно отметить, что существует множество отличных библиотек, помогающих управлять формами в React-проектах. Одна из них — это formik. Но прежде чем эффективно пользоваться любой библиотекой, нужно потратить некоторое время на её изучение. Часто это приводит к тому, что в маленьких проектах подобные библиотеки использовать просто бессмысленно. В особенности — если над проектом работает команда разработчиков, не все из которых знакомы с некоей библиотекой.

Но это не значит, что у нас не может быть простых абстракций, представляющих какие-то фрагменты кода, которым мы пользуемся достаточно часто. Один из таких фрагментов кода, который я обычно оформляю в виде самостоятельной сущности, связан с проверкой форм на наличие в них ошибок. Вот — реализация простого хука useTrackErrors, способного помочь в решении этой задачи:

import React, { useState } from "react"; import FormControl from "./FormControl"; import Input from "./Input"; import onSignup from "./SignupAPI";  export const useTrackErrors = () => {   const [errors, setErrors] = useState({});    const setErrors = (errsArray) => {     const newErrors = { ...errors };     errsArray.forEach(({ key, value }) => {       newErrors[key] = value;     });      setErrors(newErrors);   };    const clearErrors = () => {     setErrors({});   };    return { errors, setErrors, clearErrors }; }; 

Вот как можно пользоваться этим хуком:

import React, { useState } from "react"; import FormControl from "./FormControl"; import Input from "./Input"; import onSignup from "./SignupAPI";  export default function Client() {   const { errors, setErrors, clearErrors } = useTrackErrors();    const [name, setName] = useState("");   const [email, setEmail] = useState("");    const handleSignupClick = () => {     let invalid = false;      const errs = [];     if (!name) {       errs.push({ key: "name", value: true });       invalid = true;     }     if (!email) {       errs.push({ key: "email", value: true });       invalid = true;     }     if (invalid) {       setErrors(errs);       return;     }      onSignup(name, email);     clearErrors();   };    const handleNameChange = (e) => {     setName(e.target.value);     setErrors([{ key: "name", value: false }]);   };    const handleEmailChange = (e) => {     setEmail(e.target.value);     setErrors([{ key: "email", value: false }]);   };    return (     <div>       <FormControl isInvalid={errors["name"]}>         <FormLabel>Full Name</FormLabel>         <Input           onKeyDown={handleKeyDown}           onChange={handleNameChange}           value={name}           placeholder="Your name..."         />       </FormControl>       <FormControl isInvalid={errors["email"]}>         <FormLabel>Email</FormLabel>         <Input           onKeyDown={handleKeyDown}           onChange={handleEmailChange}           value={email}           placeholder="Your email..."         />       </FormControl>       <button onClick={handleSignupClick}>Sign Up</button>     </div>   ); } 

Хук useDebounce

То, что называется «debouncing», способно найти применение в любом приложении. В частности, речь идёт об уменьшении частоты выполнения ресурсоёмких операций. Например, это — предотвращение вызова API поиска данных после каждого нажатия на клавишу в ходе ввода пользователем поискового запроса. Обращение к API будет выполнено после того, как пользователь завершит ввод данных. Хук useDebounce упрощает решение подобных задач. Вот — его простая реализация, которая основана на AwesomeDebounceLibrary:

import AwesomeDebouncePromise from "awesome-debounce-promise";  const debounceAction = (actionFunc, delay) =>   AwesomeDebouncePromise(actionFunc, delay);  function useDebounce(func, delay) {   const debouncedFunction = useMemo(() => debounceAction(func, delay), [     delay,     func,   ]);    return debouncedFunction; } 

Вот — практический пример использования этого хука:

import React from "react";  const callAPI = async (value) => {   // вызов “дорогого” API };  export default function Client() {   const debouncedAPICall = useDebounce(callAPI, 500);    const handleInputChange = async (e) => {     debouncedAPICall(e.target.value);   };    return (     <form>       <input type="text" onChange={handleInputChange} />     </form>   ); } 

Используя этот хук стоит учитывать одну вещь: надо проконтролировать, чтобы «дорогая» функция не пересоздавалась бы при каждом рендеринге компонента. Дело в том, что это приведёт к сбросу «замедленной» версии этой функции и сотрёт всё из её внутреннего состояния. Обеспечить вышеописанное требование можно двумя путями:

  1. Можно объявить «дорогую» функцию за пределами функционального компонента (так, как сделано в примере).
  2. Можно обернуть такую функцию с помощью хука useCallback.

Итоги

Существует немало хороших библиотек, в которых реализованы самые разные хуки. Если вас интересуют подобные библиотеки — можете начать знакомство с ними отсюда. Но, хотя в нашем распоряжении имеется множество полезных пользовательских хуков, хочу отметить, что те пять, о которых я рассказал — это те самые хуки, которые пригодятся в любом React-проекте.

Какими React-хуками вы пользуетесь чаще всего?

ссылка на оригинал статьи https://habr.com/ru/company/ruvds/blog/554280/


Комментарии

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

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