Я не могу получить доступ к localStorage в Nextjs. Я могу использовать его в своем коде, и все работает как положено, и в ожидаемой работе ошибок нет. Однако терминал выдает ошибку:
ReferenceError: localStorage is not defined
at MainGrid (./app/components/WeightDisplay/MainGrid.tsx:17:96)
8 | // Initialize states for preferences and retrieve from local storage
9 | const [metricKg, setMetricKg] = useState(
> 10 | JSON.parse(localStorage.getItem("metricKg") || "") || false
| ^
11 | );
Вот мой код, никакие другие файлы не вызывают этой проблемы:
"use client";
import React, { useEffect, useState } from "react";
import Entry from "./Entry";
import useEntryModal from "@/app/hooks/useEntryModal";
const MainGrid = () => {
// Initialize states for preferences and retrieve from local storage
const [metricKg, setMetricKg] = useState(
JSON.parse(localStorage.getItem("metricKg") || "") || false
);
const [metricCm, setMetricCm] = useState(
JSON.parse(localStorage.getItem("metricCm") || "") || false
);
const [compactView, setCompactView] = useState(
JSON.parse(localStorage.getItem("compactView") || "") || false
);
let entry = {
num: 0,
date: "Apr 4th",
weight: 52, // kg by default
waist: 50, // cm by default
hip: 30, // cm by default
};
const entryModal = useEntryModal();
const toggleWeightUnit = () => {
setMetricKg((prevMetricKg: any) => !prevMetricKg);
};
const toggleLengthUnit = () => {
setMetricCm((prevMetricCm: any) => !prevMetricCm);
};
const toggleViewMode = () => {
setCompactView((prevCompactView: any) => !prevCompactView);
};
// Save preferences to local storage whenever they change
useEffect(() => {
localStorage.setItem("metricKg", JSON.stringify(metricKg));
}, [metricKg]);
useEffect(() => {
localStorage.setItem("metricCm", JSON.stringify(metricCm));
}, [metricCm]);
useEffect(() => {
localStorage.setItem("compactView", JSON.stringify(compactView));
}, [compactView]);
return (
<div className = "flex flex-col w-[90%] mx-auto p-4 gap-0 text-white">
<div className = "flex flex-col">
<h1 className = "text-7xl milky-walky text-white text-center w-fit mx-auto jura my-6 mb-0 relative">
Entry History
<button
onClick = {() => entryModal.onOpen()}
className = "absolute top-[50%] translate-y-[-50%] right-[-5rem] centrion"
>
+
</button>
</h1>
<div className = "flex space-x-2 gap-0">
<div className = "m-6 flex gap-3 border-r-2 border-neutral-600 py-2 pr-8">
Lbs
<label htmlFor = "one">
<input
id = "one"
type = "checkbox"
onClick = {toggleWeightUnit}
checked = {metricKg}
/>
</label>
Kgs
</div>
<div className = "m-6 flex gap-3 border-r-2 border-neutral-600 py-2 pr-8">
Inch
<label htmlFor = "one">
<input
id = "one"
type = "checkbox"
onClick = {toggleLengthUnit}
checked = {metricCm}
/>
</label>
Cm
</div>
{/* View mode toggle */}
<div className = "m-6 flex gap-3 py-2 pl-6">
Compact
<label htmlFor = "compactToggle">
<input
id = "compactToggle"
type = "checkbox"
onClick = {toggleViewMode}
checked = {!compactView}
/>
</label>
Comfortable
</div>
</div>
</div>
{/* Apply gap based on the view mode */}
<div
className = {` bg-neutral-800/75 flex flex-col ${
compactView ? " py-2" : "gap-0"
}`}
>
<Entry
entry = {entry}
metricKg = {metricKg}
compactView = {compactView}
metricCm = {metricCm}
/>
<Entry
entry = {entry}
metricKg = {metricKg}
compactView = {compactView}
metricCm = {metricCm}
/>
<Entry
entry = {entry}
metricKg = {metricKg}
compactView = {compactView}
metricCm = {metricCm}
/>
</div>
</div>
);
};
export default MainGrid;
Я попытался добавить проверку useEffect, которая должна проверять наличие localStorage, и если она недоступна, назначать значения по умолчанию, но она всегда говорит, что localStorage не определен, даже если это так.





Ошибка возникает из-за того, что компоненты клиента и сервера сначала отображаются на сервере. Директива «use client» лишь сообщает, что компонент включает в себя интерактивные элементы, которые должны выполняться на клиенте.
Поэтому элемент окна не определен во время рендеринга сервера, и из-за этого локальное хранилище также недоступно.
Это должно работать нормально, если КАЖДОЕ появление локального хранилища находится в ловушке useEffect, даже те, которые сейчас находятся внутри ловушки useState как значение по умолчанию.
Поэтому убедитесь, что вы никогда не используете локальное хранилище за пределами хука эффекта использования, и тогда все должно работать нормально.
И причина, по которой он все еще работает, заключается в том, что он работает, когда клиент берет на себя управление. Отображаемая ошибка связана с рендерингом на стороне сервера.
Когда я помещаю все возможные вещи, связанные с localStorage, внутрь useEffect и обрабатываю случай, когда localStorage недоступен, он всегда говорит, что он недоступен, и сбрасывает значения по умолчанию. Кроме того, он обновляет значения, просто в начале, когда страница монтируется, у него по какой-то причине нет к ней доступа.
В вашем коде есть несколько проблем, но давайте разберемся с ними шаг за шагом. Эта часть будет выполнена на client и на server:
const [metricKg, setMetricKg] = useState(
JSON.parse(localStorage.getItem("metricKg") || "") || false // <--
);
это эквивалентно:
const initalMetricKg = JSON.parse(localStorage.getItem("metricKg") || "") || false;
const [metricKg, setMetricKg] = useState(initalMetricKg);
localStorage.getItem("compactView") неявно пытается получить доступ window к тому, что недоступно на сервере. Есть много способов это исправить. Например, мы можем сделать это с помощью оператора цепочки ?. и явного доступа к window:
const initalMetricKg = JSON.parse(window?.localStorage?.getItem("metricKg") || "") || false;
const [metricKg, setMetricKg] = useState(initalMetricKg);
или, что ещё лучше, мы можем извлечь эту логику в функцию:
function tryGetJsonFromLocalStorage(key, defaultValue) {
return JSON.parse(window?.localStorage?.getItem(key) || "") || defaultValue;
}
const MainGrid = () => {
// Initialize states for preferences and retrieve from local storage
const [metricKg, setMetricKg] = useState(
tryGetJsonFromLocalStorage("metricKg", false)
);
.. the rest of your code
И еще одна вещь, на которую следует обратить внимание: если элемент отсутствует в localStorage, JSON.parse("") всегда будет терпеть неудачу с Uncaught SyntaxError: Unexpected end of JSON input. Итак, давайте исправим это:
function tryGetJsonFromLocalStorage(key, defaultValue) {
const str = window?.localStorage?.getItem(key);
if (!str) {
return defaultValue;
}
return JSON.parse(str) || defaultValue;
}
const MainGrid = () => {
// Initialize states for preferences and retrieve from local storage
const [metricKg, setMetricKg] = useState(
tryGetJsonFromLocalStorage("metricKg", false)
);
.. the rest of your code
мы могли бы пойти еще дальше и реализовать собственный хук для обработки логики обновления. Нет необходимости использовать useEffect в компоненте:
"use client";
import React, { useEffect, useState } from "react";
import Entry from "./Entry";
import useEntryModal from "@/app/hooks/useEntryModal";
function tryGetJsonFromLocalStorage(key, defaultValue) {
const str = window?.localStorage?.getItem(key);
if (!str) {
return defaultValue;
}
return JSON.parse(str) || defaultValue;
}
function useLocalStorageState(key, initialValue) {
const [value, setValue] = useState(
tryGetJsonFromLocalStorage(key, initialValue)
);
useEffect(() => {
// useEffect will never execute on the server but we can be extra-safe here :)
window?.localStorage?.setItem(key, JSON.stringify(value));
}, [value]);
return [value, setValue];
}
const MainGrid = () => {
// Initialize states for preferences and retrieve from local storage
const [metricKg, setMetricKg] = useLocalStorageState("metricKg", false);
const [metricCm, setMetricCm] = useLocalStorageState("metricCm", false);
const [compactView, setCompactView] = useLocalStorageState("compactView", false);
const toggleWeightUnit = () => {
setMetricKg((prevMetricKg: any) => !prevMetricKg);
};
const toggleLengthUnit = () => {
setMetricCm((prevMetricCm: any) => !prevMetricCm);
};
const toggleViewMode = () => {
setCompactView((prevCompactView: any) => !prevCompactView);
};
let entry = {
num: 0,
date: "Apr 4th",
weight: 52, // kg by default
waist: 50, // cm by default
hip: 30, // cm by default
};
const entryModal = useEntryModal();
... rest of your code without useEffects
Сервер не знает, что такое window, localstorage, sessionstorage и т. д.