export default function Page({ data1 }) {
const [bookmark, setBookmark] = useState(
typeof window !== 'undefined'
? JSON.parse(localStorage.getItem('bookmark'))
: []
);
const addToBookmark = (ayatLs) => {
setBookmark([...bookmark, ayatLs]);
};
useEffect(() => {
localStorage.setItem('bookmark', JSON.stringify(bookmark));
}, [bookmark]);
return (
<>
<div className="modal-body">
<ul>
{bookmark.map((x) => (
<li key={x.nomor}>{x.tr}</li>
))}
</ul>
</div>
</>
);
}
{data1.map((x)) => (
<div className="pb-6 flex justify-between">
<span
onClick={() => addToBookmark(x)}
className={`text-sm `}>
{x.tr}
</span>
</div>
)}
When i use typeof window !== 'undefined'
the Error: Hydration failed because the initial UI does not match what was rendered on the server in my code like this
.
and when i change to localStorage.getItem('bookmark')
the error is localStorage is not defined
when i click addToBookmark it will store data from props data1 to localStorage, no problem here, but when I fetch the data earlier in localStorage and I want to map the data that error appears
What's wrong with my code, why can't I map data from localStorage, please help me solve this problem.
CodePudding user response:
The issue is that Next.js pre-renders pages by default, which means they are rendered on the server first and then sent to the client to be hydrated.
The error comes when you're setting the default value of the state based on this condition: typeof window !== 'undefined'
.
Consider the following example:
const Page = () => {
const [name, setName] = useState(typeof window !== 'undefined' ? 'Peter' : 'Rick')
return <h1>{name}</h1>
}
export default Page
This code will throw an error of the type Error: Text content does not match server-rendered HTML.
If you inspect the page's source, you will see that the name rendered on the server was "Rick" while on the client-side's first render the name rendered was "Peter". There must be a match between the server-side rendered content and the client-side first render's content.
What you can do is move the localStorage
data gathering and parsing to another useEffect
instead and set the state in there. This solves the issue because useEffect
only runs on the client-side, therefore you will effectively match both server-side and client-side first render content.
export default function Page({ data1 }) {
const [bookmark, setBookmark] = useState([])
const addToBookmark = (ayatLs) => {
setBookmark([...bookmark, ayatLs])
}
useEffect(() => {
if (bookmark.length === 0) return
localStorage.setItem('bookmark', JSON.stringify(bookmark))
}, [bookmark])
useEffect(() => {
const bookmarkFromLocalStorage = localStorage.getItem('bookmark')
const parsedBookmark =
bookmarkFromLocalStorage !== null
? JSON.parse(bookmarkFromLocalStorage)
: []
setBookmark(parsedBookmark)
}, [])
return (
<>
<div className='modal-body'>
<ul>
{bookmark.map((x) => (
<li key={x.nomor}>{x.tr}</li>
))}
</ul>
</div>
</>
)
}