Связываем React и localStorage через useSyncExternalStore
— 4 минуты
Как согласовать изменение состояния в реакте и поля в localStorage?
До недавнего времени самым простым вариантом было создать контекст с внутренним React состоянием и обрабатывать всё взаимодействие с localStorage через него — вариант рабочий, но далеко не идеален: легко напороться на ререндеры, много кода писать нужно ну и вот это вот всё
Также можно обработать какое-то не-реактовое значение через комбинацию useState + useEffect, но это ещё менее надёжно, ведь браузерные значения могут меняться и без уведомления реакта, и, соответственно, без ререндера
Красиво в одной из статей на хабре описали:
Для работы с состоянием в React используются хуки useState и useReducer, но они не умеют работать с состоянием, которое "живет" за пределами React, поскольку в один момент времени доступна только одна версия внешнего состояния.
Значения внешнего состояния могут меняться со временем без ведома React, что может приводить к таким проблемам, как отображение двух разных значений для одних и тех же данных.
Но не так давно в 18 версию React добавили хук useSyncExternalStore, который такую задачу решает намного изящнее
Многие скипнули его и даже не знают зачем он нужен, что, в целом, достаточно ожидаемо, ведь даже команда разработчиков позиционировала его больше как хук для разработчиков библиотек, а мы тут далеко не все пишем свои либы
Короче, что это за хук вообще? Очень просто — этот хук нужен для более глубокой интеграции внешних хранилищ в модель React. Говоря проще — хук нужен для того, чтобы триггерить рендер из внешних хранилищ, а не только через setState функции
Как раз этот хук и поможет нам интегрироваться с localStorage сильно проще и безопаснее. Тут localStorage в понятие внешнего хранилища ложится просто шикарно
На коленке код будет выглядеть примерно так:
typescript1const useLocalStorageState = (key: string, defaultValue?: string) => { 2 const subscribe = (listener: () => void) => { 3 window.addEventListener("update-local-storage", listener); 4 return () => void window.removeEventListener("update-local-storage", listener); 5 }; 6 7 const getSnapshot = () => localStorage.getItem(key) ?? defaultValue; 8 9 const store = useSyncExternalStore(subscribe, getSnapshot); 10 11 const updateStore = (newValue: string) => { 12 localStorage.setItem(key, newValue); 13 window.dispatchEvent(new StorageEvent("update-local-storage", { key, newValue })); 14 }; 15 16 return [store, updateStore] as const; 17}; 18
В чём тут идея:
- При вызове updateStore будем помимо изменения значения в localStorage диспатчить на window ещё и StorageEvent с ключом, например, "update-local-storage"
- В функции подписки subscribe объясним когда нужно вызывать getSnapshot для получения актуального состояния из внешнего хранилища и когда от его прослушивания нужно отписаться. Можно воспринимать как эффект
Использовать будем как обычный useState:
typescript1const [name, setName] = useLocalStorageState("name", "progway"); 2
Теперь хук при вызове с одним и тем же ключом к localStorage (name в примере выше) будет обновлять все зависимые компоненты при регистрации события "update-local-storage" на window
Используя тот же подход, можно реализовать порой очень полезные хуки useMediaQuery, useWindowSize и другие. О первых двух можно прочитать в статье от Timeweb Cloud
Статья была полезной?
Читайте также:
— 2 минуты
Что такое Server-Sent Events
SSE — это технология для однонаправленного соединения между сервером и клие...
— 3 минуты
Теги для шаблонных строк
В JavaScript есть, как по мне, крайне странный синтаксис. Самым очевидным е...
— 3 минуты
Составные компоненты
Есть такой паттерн для реакта, который называется Compound Components. Это...