useLocalStorage mismatches server render
Any server rendering solution won't have access to a browser's local storage, so it will default to the initialValue. Since useLocalStorage will use the actual local storage value on first render, this will immediately end up in a react hydration mismatch anytime the local storage value is not equal to the initialValue.
What would be possible solutions?
Have the state be some sane default (maybe specified by the user, maybe null) and then fill it with local storage in a useEffect, which only happens on the browser. It would be up to the user how to handle the default.
I believe currently it returns a default value (undefined by default) on the server: https://github.com/streamich/react-use/blob/master/src/useLocalStorage.ts#L9
And sets that value in useEffect hook on the browser: https://github.com/streamich/react-use/blob/master/src/useLocalStorage.ts#L32
Do you mean it should return undefined on the browser, too?
At least on the first render, yeah. If you console output the value, you can see what I mean. In the server, it'll be the initial value, but on the browser the first render will be whatever is in local storage, which means the hydration won't match.
Maybe we can add an option, if true, that would on the browser return the default value first, and only then re-render with a real value?
That would solve my issues!
You can also try disabling SSR for the component that is using local storage state.
In Next.js, for instance, you could do this:
import dynamic from 'next/dynamic'
const DynamicComponentWithNoSSR = dynamic(
() => import('../components/hello3'),
{ ssr: false }
)
https://nextjs.org/docs/advanced-features/dynamic-import#with-no-ssr
@dlbnco this avoids the problem by removing all the benefits of SSR for that component, which seems like a regression rather than a progression of features. The solution that doesn't disable SSR benefits to use the hook seems like the way to go.
@saiichihashimoto that is true, although you could also use the loading option to load the same component (without state from localstorage) as a placeholder on SSR. Local storage is not available on the server anyway, so I think it would not cause any regression.
https://nextjs.org/docs/advanced-features/dynamic-import#with-custom-loading-component
@dlbnco that's one way to solve it. What I'm claiming is that the hook's issue can be isolated to the hook, rather than requiring the entire component be blocked out via SSR. This lets the implementer decide how they want to handle the situation where the value isn't loaded on the frontend rather than the blanket solution where they don't show the component, since the component itself has no issues showing.
SSR is well supported in upcoming hook of @react-hookz/web react-hookz/web#43
Quick update: SSR is now fully supported by the useLocalStorageValue and useSessionStorageValue hooks included in @react-hookz/web! For reference, here are the docs on useLocalStorageValue and useSessionStorageValue.
If you are thinking of migrating, we have created a handy react-use to @react-hookz/web migration guide. Hope it helps!
This works for me.
import { useRendersCount, useLocalStorage } from 'react-use'
const defaultValue = 'default'
const ShowValue = () => {
const [value, setValue] = useLocalStorage('value', defaultValue)
const isFirst = useRendersCount() === 1;
return (
<>
<button type="button" onClick={() => setValue('new value')}>Set value </button>
{isFirst ? defaultValue : value}
</>
)
}
I use useLocalStorage in nextjs 13, and it still throws error
@streamich Would you like a PR on this one ? It is indeed quite annoying, I am using Next 13 too and it creates big errors