
Введение
Состояние в React является мощным инструментом обновления пользовательского интерфейса, при использовании которой React берет на себя задачи обновления и перерендеривания DOM-дерева. В большинстве современных приложений используются функциональные компоненты, в которых возможно использование хука useState.
Однако, использование useState имеет недостатки. Одним из них является тот факт, что хранимое в компонентах состояние не сохраняется с обновлением страницы, что может ухудшить UX веб-приложения.
Проблема
Представьте, например, что вам потребовался новый компьютер (потому-что ваш старый комп начал безбожно тупить), и вы зашли на сайт с каталогом техники для поиска подходящей модели. У вас, как у уверенного ПК-пользователя имеется целый ряд критериев к новому девайсу. И вот вы указываете в фильтрах объем оперативной памяти, частоту процессора, модель видеокарты, цену, и много-много чего ещё…
И вот вы, потратив своё драгоценное время на подборку параметров, которые подходят именно вам, наконец определились с критериями и наконец смотрите подходящие варианты. Но вдруг вы случайно обновили страничку и… Все ваши фильтры сбросились. Согласитесь, ситуация мягко говоря неприятная, и ласковых слов разработчикам за такое поведение сайта точно не скажешь.
Query-параметры
Как можно предотвратить подобный сброс состояния приложения? Одним из способов решения проблемы может стать использование URL-строки браузера, а точнее её query-параметров:
https://website.ru/catalog?cpu_cores=6
Здесь мы храним единственный фрагмент состояния поиска - нужное количество ядер процессора – gpu_cores. Одного этого параметра для тщательного подбора может не хватить, поэтому можно добавить дополнительные параметры:
https://website.ru/catalog?cpu_cores=6&ram_size=32&gpu_model=rtx_3090
Данный пример уже выглядит более интересно, поскольку содержит в себе сразу 3 параметра – cpu_cores, ram_size и gpu_model.
Дополнительные возможности
Ещё одно преимущество, которое дает хранение состояния в URL – возможность сохранения состояния. На нашем примере с сайтом по подбору техники это даст возможность, например, сохранения в закладках браузера страницы с подготовленными фильтрами, что позволит вернуться пользователю на страницу с подготовленными фильтрами!
Хранение состояния приложения в URL также позволит пользователям веб-приложения делиться друг с другом страницами с подготовленным состоянием – если, например, вы подобрали значения фильтров на странице и решили поделиться страницей с другом, то он при открытии сайта сможет увидеть контент на странице, подобранный по вашим параметрам фильтрации.
Библиотека use-query-params
Подготовка
Для работы библиотеки use-query-params в первую очередь её необходимо загрузить, сделать это можно с помощью команд:
npm i use-query-params
или
yarn add use-query-params
или
bun i use-query-params
Использование данной библиотеки предполагает, что вы используете React Router в качестве решения для маршрутизации react-приложения.
Для работы библиотеки необходимо обернуть приложение в компонент QueryParamProvider, предоставив адаптер для соответствующего router'а. Ниже представлен пример данной настройки с официальной страницы библиотеки:
import React from 'react';
import ReactDOM from 'react-dom/client';
import { QueryParamProvider } from 'use-query-params';
import { ReactRouter6Adapter } from 'use-query-params/adapters/react-router-6';
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import App from './App';
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<BrowserRouter>
<QueryParamProvider adapter={ReactRouter6Adapter}>
<Routes>
<Route path="/" element={<App />}>
</Routes>
</QueryParamProvider>
</BrowserRouter>,
document.getElementById('root')
);
Обратите внимание, что QueryParamProvider находится внутри BrowserRouter, а не наоборот – такое расположение важно для корректной работы библиотеки.
useQueryParam
В рамках использования URL-параметров для хранения состояния можно воспользоваться библиотекой use-query-params. Данная библиотека предоставляет хук useQueryParam, работающий схожим образом с хуком useState:
import { FC } from 'react';
import { useQueryParam, NumberParam, StringParam } from 'use-query-params';
const QueryParamExample: FC = () => {
const [cpuCores, setCpuCores] = useQueryParam('cpu_cores', NumberParam);
const [gpuModel, setGpuModel] = useQueryParam('gpu_model', StringParam);
return (
...
);
}
В данном примере мы используем хук useQueryParam для обработки двух параметров – cpuCores и gpuCores, в качестве аргументов, передаваемых хуку, указывается название переменной в URL и тип параметра.
В качестве возвращаемого значения хук отдает значение состояния и функцию её изменения. При этом тип значения, возвращаемого хуком, зависит от указанного типа параметра – cpuCores будет иметь тип number | null | undefined, а gpuModel будет иметь тип string | null | undefined.
Библиотека предоставляет список поддерживаемых типов параметров, среди которых числовые и строковые примитивы, булевые значения, объекты Date, массивы, JSON-объекты и т.д. – с полным списком можно ознакомиться на странице документации.
При вызове функции изменения состояния, возвращаемой хуком useQueryParam, значение соответствующего параметра будет изменяться не только в рамках состояния приложения, но и в рамках значения указанного URL-параметра.
Например, вызов setCpuCores(8) приведет к изменению URL-адреса:
https://website.ru/catalog?cpu_cores=6
После этого, при вызове setGpuModel('radeon_r6') получим:
https://website.ru/catalog?cpu_cores=6&gpu_model=radeon_r6
При обновлении страницы с указанными URL-параметрами значениям параметров cpuCores и gpuModel будут автоматически присвоены значения из URL.
useQueryParams
В примере работы с хуком useQueryParam мы использовали его для отслеживания состояния двух параметров, относящихся к фильтрации компьютеров. Однако нет никаких сомнений, что для подборки ПК может потребоваться большее количество различных параметров. Что же, в таком случае писать для каждого из них свой useQueryParam? Не было бы разумнее объединить схожие по назначению параметры в один объект и использовать его? Такую задачу позволяет решить хук useQueryParams.
import { FC } from 'react';
import { useQueryParam, NumberParam, StringParam } from 'use-query-params';
const QueryParamsExample: FC = () => {
const [cpuCores, setCpuCores] = useQueryParam('cpu_cores', NumberParam);
const [gpuModel, setGpuModel] = useQueryParam('gpu_model', StringParam);
const [filters, setFilters] = useQueryParams({
cpu_cores: NumberParam,
gpu_model: StringParam,
ram_size: NumberParam,
});
const { cpu_cores: cpuCores, gpu_model: gpuModel, ram_size: ramSize } = filters;
return (
...
);
}
В данном примере возвращаемый объект filters будет содержать в качестве атрибутов параметры cpu_cores, gpu_model и ram_size. Деструктуризировав filters, можно получить значения фильтров.
В данном примере при вызове setFilters({cpu_cores: 12, gpu_model: 'rtx4080', ram_size: 32}) произойдет изменение значения этих параметров в URL-адресе:
https://website.ru/catalog?cpu_cores=12&gpu_model=rtx4080&ram_size=32
При вызове, например, setFilters({ram_size: 64}), по-умолчанию произойдет изменение только параметра ram_size, значения остальных параметров останутся прежними:
https://website.ru/catalog?cpu_cores=12&gpu_model=rtx4080&ram_size=64
Заключение
В данной статье были кратко рассмотрены базовые возможности библиотеки use-query-params для решения задачи синхронизации состояния React-приложения с query-параметрами URL-адреса. В действительности, эта библиотека умеет больше, чем было показано здесь, поэтому, если вас заинтересовала возможность данного инструмента, welcome.
Однако, при использовании данной библиотеки имеется ньюанс – как вы могли заметить, use-query-params работает только при использовании React Router. А что, если, по каким-либо причинам, на вашем проекте не используется этот инструмент маршрутизации? В таком случае предлагаю вам реализовать собственный хук обработки query-параметров, о чем пойдет речь в следующей части.