Я создал собственный реагирующий хук, который позволяет пользователям переключать цветовую тему моего приложения (т. е. «светлая» или «темная»), и изменение состояния не приводит к повторному рендерингу компонентов, вызывающих хук.
Крючок называется useColorTheme
и определяется следующим образом:
import { useEffect } from "react";
import useLocalStorage from "./useLocalStorage";
export default function useColorTheme() {
const [colorTheme, setColorTheme] = useLocalStorage("color-theme", "light");
useEffect(() => {
const className = "dark";
const bodyClass = window.document.body.classList;
colorTheme === "dark"
? bodyClass.add(className)
: bodyClass.remove(className);
}, [colorTheme]);
return [colorTheme, setColorTheme];
}
Как видите, этот хук вызывает другой хук под названием useLocalStorage
, который позволяет сохранять состояние при обновлении.
Я получил этот хук от usehooks.com, и он определяется как:
import { useState } from "react";
const PREFIX = "my-app-";
export default function useLocalStorage(key: string, initialValue: string) {
const prefixedKey = PREFIX + key;
const [storedValue, setStoredValue] = useState(() => {
if (typeof window === "undefined") {
return initialValue;
}
try {
const item = window.localStorage.getItem(prefixedKey);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.info(error);
return initialValue;
}
});
function setValue(value: unknown) {
try {
/**
* Don't fully understand function typing here
*/
const valueToStore =
value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
if (typeof window !== "undefined") {
window.localStorage.setItem(prefixedKey, JSON.stringify(valueToStore));
}
} catch (error) {
console.info(error);
}
}
return [storedValue, setValue];
}
Значение colorTheme
успешно сохраняется в localStorage
, и хук работает при начальной загрузке приложения, но у меня возникают проблемы с компонентами, которые вызывают useColorTheme
хук, как в этом DrawerContainer
примере:
export default function DrawerContainer() {
// calling the hook
const [theme, setTheme] = useColorTheme();
// component does not re-render when ThemeToggle component toggles theme state
return (
<div className = "flex items-center justify-between gap-2">
<FiMenu size = {30} className = "lg:hidden mr-4 cursor-pointer" />
<Image
className = "ml-10 mr-10 sm:mr-2 sm:ml-0 cursor-pointer"
src = {
theme === "light"
? "/images/fiverr-logo.png"
: "/images/fiverr-logo-white.png"
}
alt = "logo"
width = {100}
height = {100}
/>
{!user && <AuthButtons showUnderSmall />}
<ThemeToggle />
</div>
);
}
Когда значение colorTheme
переключается в каком-либо другом компоненте моего приложения (например, в моем компоненте ThemeToggle
), измененное состояние не фиксируется в моем компоненте DrawerContainer
, что предотвращает выполнение логики, считывающей это состояние.
Я убедился, что состояние действительно меняется в моем браузере Dev Tools, так почему мой компонент DrawerContainer
не перерисовывается?
Заранее большое спасибо. Любое понимание очень ценится.
Не просматривайте ваш код и не смотрите на проблему.
Ваш пользовательский хук независим, это означает, что в каждом компоненте он имеет свое собственное состояние.
Так, например, у вас есть два компонента A, B, и оба используют хук.
когда вы меняете theme
с помощью компонента A
, новое состояние заключается внутри компонента A
и не передается B
или другим компонентам, которые также используют хук.
Чтобы решить вашу проблему, вы должны использовать Context API и использовать одно состояние, которое будет передаваться другим компонентам с использованием контекста.
Посмотрите это https://reactjs.org/docs/context.html
Это может быть реализовано так (псевдокод):
1.Создание контекста
const themeContext = React.createContext();
2. Реализация хука, который будет повторно использоваться в компонентах для перехвата состояния контекста.
function useTheme() {
return useContext(themeContext);
}
3. Реализация компонента поставщика, который следует использовать на корневом уровне, чтобы его можно было использовать во всех дочерних компонентах.
const {Provider} = themeContext;
function ThemeProvider(props) {
const [theme,setTheme] = useState(() => //grabbing initial theme);
const context = useMemo(() => ({theme, setTheme}), [theme])
return <Provider value = {context}>{props.children}</Provider>
}
4. Компоненты упаковки, которые будут использовать ваш контекст.
function App() {
return (
<ThemeProvider>
<MyComponent />
</ThemeProvider>
)
}
Надеюсь, поможет!
Это имеет большой смысл, спасибо!
У вас есть что-то вроде
CodeSandbox
, чтобы более эффективно разобраться в проблеме?