Когда в проекте React (Next.js) требуется внедрить глобальное состояние, которое должно быть доступно в различных компонентах не только для чтения, но и для изменения, то на ум приходят такие решения, как Zustand, Jotai, Nanostore и т.п. Каждая из этих библиотек имеет свои преимущества и недостатки, но в этой статье речь пойдет немного о другом.
В большинстве случаев для внедрения глобального состояния можно обойтись стандартным функционалом React, добавив в проект всего один файл из нескольких строк кода. А подключение сторонней библиотеки является неоправданным и избыточным.
1. Создаём провайдер глобального состояния ValueProvider, которое должно быть доступно для чтения value и изменения setValue() в любом месте проекта:
"use client";
import { createContext, useContext, useState } from "react";
const ValueContext = createContext({
value: 0,
setValue: null,
});
export default function ValueProvider({ children }) {
const [value, setValue] = useState(0);
return (
<ValueContext value={{ value: value, setValue: setValue }}>
{children}
</ValueContext>
);
}
export function useValue() {
return useContext(ValueContext);
}В этом примере глобальное состояние value является числовым, но может быть любого типа, включая сложные объекты.
2. Подключаем провайдер глобального состояния в корневом макете:
import "./globals.css";
import ValueProvider from "@/lib/value-provider";
export default function RootLayout({ children }) {
return (
<html lang="ru">
<body>
<ValueProvider>{children}</ValueProvider>
</body>
</html>
);
}Теперь глобальное состояние value и функция его изменения setValue() будут доступны в любом месте проекта.
3. Пример использования глобального состояния value в компоненте:
"use client";
import { useValue } from "@/lib/value-provider";
export default function Component() {
const { value, setValue } = useValue();
return <button onClick={() => setValue((n) => n + 1)}>Value: {value}</button>;
}В этом компоненте используется как само глобальное состояние value, так и функция его изменения setValue(). Но это решение позволяет в одних компонентах использовать только значение глобального состояния value, а в других – только функцию его изменения setValue().
Провайдер с use client не делает все дочерние компоненты клиентскими. Это распространенное заблуждение, правило работает иначе. Директива use client определяет границу (boundary) между серверным и клиентским кодом на уровне модулей, а не дерева рендеринга:
Сам компонент (в нашем случае – провайдер), в котором указана директива, и все модули, которые он импортирует напрямую, становятся частью клиентского бандла.
Дочерние компоненты, переданные как children (или через другие пропсы), не становятся клиентскими автоматически. Они сохраняют свой изначальный статус (серверный или клиентский).