rooks icon indicating copy to clipboard operation
rooks copied to clipboard

SSR-safe useLocalstorageState

Open jekh opened this issue 2 years ago • 3 comments

Is your feature request related to a problem? Please describe. useLocalstorageState can result in hydration errors due to differing values on the initial server-side and client-side renders, based on the value in local storage. For example:

Warning: Expected server HTML to contain a matching <button> in <div>.

Even though useLocalstorageState accepts a default value, the value on the first client-side render reflects client-side local storage, while the SSR value is always the default.

I assume this issue also affects useSessionstorageState, but have not verified.

Describe the solution you'd like To make useLocalstorageState work with SSR without hydration warnings, the value on the first client-side render must always match the value during SSR, which means the default value would have to be returned on the first client-side render. It's not clear whether that is a desired default for people who don't care about SSR, though.

Some potential options:

  1. Always return the default value on the first client-side render
  2. Add an option to control the above behavior, i.e. return the local storage value on first render by default but allow people to opt in to "default value on first render" behavior.
  3. Create another hook, such as useLocalstorageStateSsr, that essentially does the above, probably by wrapping it with a useEffect.
  4. Don't change any behavior, but perhaps update the documentation to warn about SSR issues and recommend wrapping the hook.

Describe alternatives you've considered Wrapping any components that use useLocalstorageState inside a <NoSsr>-style tag prevents the issue. Unfortunately it's easy to forget you need to do this, and it also eliminates the benefits of SSR for that portion of the tree.

A custom wrapper hook (Option 3) is a viable work-around.

Additional context Here's some code that demonstrates the problem:

function MyComponent() {
  const [valueFromLocalStorage, setValueFromLocalStorage] = useLocalstorageState(
    "some_local_storage_key",
    false
  )

  // During SSR, this is always false (the default value). When hydrating client-side, this depends on the value of "some_local_storage_key".
  console.log(valueFromLocalStorage)

  if (valueFromLocalStorage === true) {
    return (
      <div>
        <button onClick={() => setValueFromLocalStorage(false)}>
          Set local storage value to false
        </button>
        <p>Text below is rendered only when local storage value is true (never during SSR).</p>
      </div>
    )
  } else {
    return (
      <div>
        <p>Text above is rendered only when local storage value is false (including SSR).</p>
        <button onClick={() => setValueFromLocalStorage(true)}>
          Set local storage value to true
        </button>
      </div>
    )
  }
}

If you are using SSR, you'll see this error after clicking the button and refreshing: Warning: Expected server HTML to contain a matching <button> in <div>.

jekh avatar Jul 14 '22 21:07 jekh

@imbhargav5 I'm completely willing to take this on if you think it's a problem worth solving. Let me know which of the possible options (or any others) you think would align better with the design of rooks.

jekh avatar Jul 21 '22 17:07 jekh

@jekh I am feeling inclined towards option 2. I understand your use-case for sure. How about we add a config variable with an apt name and then if that is set, we return the default value and override the value in localstorage.

imbhargav5 avatar Jul 21 '22 18:07 imbhargav5

@jekh I am feeling inclined towards option 2. I understand your use-case for sure. How about we add a config variable with an apt name and then if that is set, we return the default value and override the value in localstorage.

That sounds good to me. I'll see what I can pull together over the coming days/week(s).

jekh avatar Jul 22 '22 02:07 jekh

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Mar 20 '23 09:03 stale[bot]