use-local-storage-state: Can cause hydration mismatch with SSR
First off, really nice hook! One issue I’m seeing for SSR’ed apps is if the stored value affects DOM structure then there’s going to be a hydration mismatch warning (for example Warning: Expected server HTML to contain a matching <div> in <div>
).
One workaround:
export default function Todos() {
const [query, setQuery] = useState('')
const [todos, setTodos] = useLocalStorageState('todos', ['buy milk'])
// Keep track of whether app has mounted (hydration is done)
const [hasMounted, setHasMounted] = useState(false);
useEffect(() => {
setHasMounted(true)
}, []);
function onClick() {
setTodos([...todos, query])
}
return (
<>
<input value={query} onChange={e => setQuery(e.target.value)} />
<button onClick={onClick}>Create</button>
{/* Only render todos after mount */}
{hasMounted && todos.map(todo => (<div>{todo}</div>))}
</>
)
}
Maybe we can include a section in the readme that covers this? I’m not sure if there’s an elegant way for the to hook to handle this. It could wait until mount to return the value from storage, but then that means an extra re-render for non-SSR apps. Thoughts?
More discussion: https://twitter.com/gabe_ragland/status/1232055916033273858 https://www.joshwcomeau.com/react/the-perils-of-rehydration/
About this issue
- Original URL
- State: closed
- Created 3 years ago
- Reactions: 4
- Comments: 23 (13 by maintainers)
I really like the idea for
useSsrMismatch
and could see myself using it in custom hooks I write and when dealing with libraries that don’t handle this. It seems like a much better solution than how I useuseHasHydrated
in my above example.I think my preference would still be to have this integrated into the library and have it be opt-out like Material UI’s
useMediaQuery
hook.So maybe the API would look like:
The logic being that Next.js and Gatsby are growing in popularity and it’s better that a non-SSR user have an unnecessary re-render than having SSR users run into subtle bugs or have to understand how to use
useSsrMismatch
. I’m assuming the performance cost ofuseLayoutEffect
is pretty small, but probably worth looking into first.The current state is — I like the idea and I am willing to implement it. I will post here when I start working on it.
I released 14.0.0 with hydration mismatch support. The library is now 30% smaller in size as well.
I hope this release makes this local storage hook the most popular 😇.
Closing this issue. Thanks to all who participated and helped.
🎉 🎉 🎉
I’m ready and I’ve published a pre-release of the new version. The new version is a complete rewrite. I will explain in detail why I decided to make the change after I receive some feedback. The new version can be installed by running
npm install use-local-storage-state@next
and the documentation can be found at https://github.com/astoilkov/use-local-storage-state/tree/future.Any feedback is very much welcome. Thanks!
I started working on this.
If you are one of the people who is waiting for this, you can upvote this comment.
Did this get anywhere? I’m also running into the same problem.
I like the noSsr feature suggested above and I also agree that it would be good to include it in the library so that developers don’t have to go out of their way to “fix” the issue when using NextJS or Gatsby and could flick an option on instead 😃
Good point. That does avoid the flicker, but results in a warning due to
useLayoutEffect
being run the server [0]. I was able to work around that by using useEffect on the server instead. In case you’re interested, this I’m now handling it in my code now.Do you think it would make sense to have an options arg for
createLocalStorageStateHook
with ability to specify that it’s being used in an SSR environment and the value should be thedefaultValue
until after hydration? This would basically replace what I’m doing above.[0] https://github.com/facebook/react/issues/14927
@astoilkov Sorry for the confusion—yes, I saw that part of the discussion, but I’m not actually using your hook. I just stumbled on this thread when researching the problem. My use-case is setting up a single app-wide flag for checking server-vs-client on an SSG NextJS site.
Yes, I will try to think of better ones.
Yes. For the apps that don’t use server-side rendering.
Perfect. Thanks for this and for all the feedback.
Yes. I will fix this.
@gragland I am now convinced that I should find a solution to this. I will think about it and let you know. Thanks for the entire discussion.