I am not able to access the localStorage in Nextjs. I can use it in my code and everything works as expected and no errors are present in the expected work. However the terminal gives the error:
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 | );
Here is my code, no other files are causing that issue:
"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;
I tryed adding the useEffect check, which sould check for localStorage and if it’s not available assign default values, but it always says that localStorage is not defined, even when it is.
2
Answers
The Error occurs because Client and Server components are both rendered on the server first. The “use client” directive only tells next that the component includes interactive elements that are to be executed on the client.
Therefore the window element is undefined during the server rendering and due to that localstorage is also unavailable.
It should work fine if EVERY occurrence of local storage is in a useEffect hook, even the ones that are now inside the useState hook as default value.
So make sure that you never use local storage outside of the use effect hook and then it should work just fine.
And the reason why it is still working is, that it works when the client takes over. The displayed error is from the server side render.
There are several problems in your code, but lets get to it step by step. This part will be executed on the
client
and on theserver
:it is an equivalent to:
localStorage.getItem("compactView")
implicitly trying to accesswindow
that does not available on server. There are many ways to fix it. For example we can do it with chaining operator?.
and explicit access towindow
:or, even better, we can extract this logic to the function:
And one more thing to notice, is that when item is not present in localStorage
JSON.parse("")
will always fail withUncaught SyntaxError: Unexpected end of JSON input
. So let’s fix it:we could take it even further and implement custom hook to handle update logic. No need to have useEffect in the component: