Хук 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(() => (<ConfirmationDialogheaderText={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><InputonKeyDown={handleKeyDown}onChange={handleNameChange}value={name}placeholder="Your name..."/></FormControl><FormControl isInvalid={errors["email"]}><FormLabel>Email</FormLabel><InputonKeyDown={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>);}
Используя этот хук стоит учитывать одну вещь: надо проконтролировать, чтобы дорогая функция не пересоздавалась бы при каждом рендеринге компонента. Дело в том, что это приведёт к сбросу замедленной версии этой функции и сотрёт всё из её внутреннего состояния. Обеспечить вышеописанное требование можно двумя путями:
- Можно объявить дорогую функцию за пределами функционального компонента (так, как сделано в примере).
- Можно обернуть такую функцию с помощью хука useCallback.
Итоги
Существует немало хороших библиотек, в которых реализованы самые разные хуки. Если вас интересуют подобные библиотеки можете начать знакомство с ними отсюда. Но, хотя в нашем распоряжении имеется множество полезных пользовательских хуков, хочу отметить, что те пять, о которых я рассказал это те самые хуки, которые пригодятся в любом React-проекте.
Какими React-хуками вы пользуетесь чаще всего?