juliencrn / usehooks-ts

React hook library, ready to use, written in Typescript.
https://usehooks-ts.com
MIT License
6.51k stars 421 forks source link

`useLocalStorage` creates warning in Next.JS #90

Closed oviirup closed 2 years ago

oviirup commented 2 years ago

Next JS prebuilds pages, so when using the useLocalStorage hook it creates a warning link

Warning: Text content did not match. Server: "Initial Value" Client: "Stored Value"
juliencrn commented 2 years ago

Hi @GrayGalaxy, thanks for the issue!

I've tried to reproduce the bug adding an useLocalStorage in the pages/index.tsx in an empty Next app and the npm run dev and npm run build then npm run start, but I haven't any warning message. How can I reproduce it?

juliencrn commented 2 years ago

I've seen your PR. I think this isn't a bug. The hook uses a window API which is only available in the browser. Thus, if we try to call it in server-side, it prints a warning message to notice it to the dev, because it could create an unexpected behavior.

Your fix works to don't read localStorage in an SSR environment, but it's a regression. The useLocalStorage hook can be used in different parts of your application with the same key, and its value must be synchronized. In this case, if the value exists in the local storage, the hook should read it directly.

If you have another idea, it's welcome.

oviirup commented 2 years ago

The warning occurs in next.js create_next_app running on local server. I use it in component level on client side. Here is the code...

const Home: NextPage = (props) => {
  const [value, setValue] = useLocalStorage<string>('value', 'Hello World')
  return (
    <div className="container">
      Index Page
      <p>{value}</p>
      <button onClick={() => { setValue('newValue') }}>This is a Button</button>
    </div>
  )
}

And here is the full error...

Warning: Text content did not match. Server: "Hello World" Client: "newValue"
    at p
    at div
    at Home (webpack-internal:///./pages/index.tsx:43:84)
    at MyApp (webpack-internal:///./pages/_app.tsx:47:27)
    at StyleRegistry (webpack-internal:///./node_modules/styled-jsx/dist/stylesheet-registry.js:231:34)
    at ErrorBoundary (webpack-internal:///./node_modules/@next/react-dev-overlay/lib/internal/ErrorBoundary.js:26:47)
    at ReactDevOverlay (webpack-internal:///./node_modules/@next/react-dev-overlay/lib/internal/ReactDevOverlay.js:90:23)
    at Container (webpack-internal:///./node_modules/next/dist/client/index.js:331:9)
    at AppContainer (webpack-internal:///./node_modules/next/dist/client/index.js:770:26)
    at Root (webpack-internal:///./node_modules/next/dist/client/index.js:891:27) 

See more info here: https://nextjs.org/docs/messages/react-hydration-error

Don't know why this happen, but it did not happened with create_react_app, probably due to caching. But using the initialValue instead of readValue() solved it.

juliencrn commented 2 years ago

Hm, weird. I've re-test your component, and I haven't any warning messages.

This is my package.json:

{
  "name": "next-app",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "next": "12.0.8",
    "react": "17.0.2",
    "react-dom": "17.0.2",
    "usehooks-ts": "^2.2.1"
  },
  "devDependencies": {
    "@types/node": "17.0.10",
    "@types/react": "17.0.38",
    "eslint": "8.7.0",
    "eslint-config-next": "12.0.8",
    "typescript": "4.5.5"
  }
}

I need more to help you, what else are you using, which versions? Would you create a bug reproduction?

oviirup commented 2 years ago

Don't know what happened, I clan installed the dependencies now it's working fine.

juliencrn commented 2 years ago

Weird, but cool if you're ok now (:

Maybe was the 2.2.1 release where i've updated how the lib was built (cjs/esm fix)

ryancwalsh commented 2 years ago

I'm having problems in Next.js too and would appreciate any tips if you have a moment: https://stackoverflow.com/questions/74022328/how-to-solve-react-hydration-error-in-next-js-when-using-uselocalstorage-and

GravlLift commented 10 months ago

@juliencrn I ran into this issue just now. Was able to resolve it by copying the useLocalStorage hook exactly, and only altering this line:

https://github.com/juliencrn/usehooks-ts/blob/8480d904dd857af2bd0cff4a19cb36fa5b44d18b/packages/usehooks-ts/src/useLocalStorage/useLocalStorage.ts#L42

to read

const [storedValue, setStoredValue] = useState<T>(initialValue)

instead.

Presumably Next just wants the initial value to be the same on both client and server.

juliencrn commented 9 months ago

435 should fixes this

GravlLift commented 9 months ago

435 should fixes this

@juliencrn, I'm not sure moving the evaluation of serverside-ness to outside the function like that PR is doing is going to work. This issue is caused by the initial state value being different on server than it is on client, simply changing where in the code that gets evaluated isn't going to change anything.

On Server: Initial state loaded with initialValue, a hardcoded value. UI is created based on that value.

On Client: Initial state loaded with whatever's in local storage. If this hook has been used before, that's very likely not initialValue.

The reason my adjustment fixed the issue is because both client and server are loaded with the value of initialValue. Loading of the local storage value needs to happen after the initial state load, not during it.

juliencrn commented 9 months ago

Both was done is that PR https://github.com/juliencrn/usehooks-ts/pull/435/files#diff-f4c5c0032b7faf8b05188d0f6b764bd8d194e66da0d7feb0b3eeb37335495ac5R44