react-hook icon indicating copy to clipboard operation
react-hook copied to clipboard

[useMediaQuery] React hydration doesn't re-evaluate the hook

Open Ralle opened this issue 2 years ago • 3 comments

Describe the bug

Next.JS does SSR and this hook sends a false first and then React doesn't update when it client-side detects a true. I made a work-around that makes it work. I am not sure what what internals make this problem occur.

To Reproduce

const isDesktop = useMediaQuery("(min-width:900px)");

I return different HTML depending on whether isDesktop is true or false and it seems like React assumes that that the HTML it got from the server was with isDesktop = true while it was when isDesktop = false. For things like this I have always made a useEffect to change the value and not useState(theCurrentValue) which would cause a discrepancy between server and browser.

Expected behavior Correctly rendered HTML.

Here is my work-around that makes this work regardless:

  const isDesktop_ = useMediaQuery("(min-width:900px)");

  const [isDesktop, setIsDesktop] = useState(false);

  // Work-around for server-side render
  useEffect(() => {
    setIsDesktop(isDesktop_);
  }, [isDesktop_]);

Ralle avatar Nov 04 '21 07:11 Ralle

This is always going to be a problem with SSR hydration. I am hesitant to add any logic to the hook to support this, because it encourages the anti-pattern of using JavaScript to change layout instead of CSS in situations where things are clearly being rendered outside of the window while simultaneously making the hook force a new render each client-side render. Probably not the answer you're looking for, but I don't see this changing. Happy to be convinced by others.

jaredLunde avatar Nov 21 '21 15:11 jaredLunde

I had the same issue with useWindowSize.

My workaround:

useSsrCompatible.tsx:

import { useState, useEffect } from "react";

export function useSsrCompatible<T>(newValue: T, initialValue: T) {
    const [value, setValue] = useState(initialValue);
    useEffect(() => {
        setValue(newValue);
    }, [newValue]);
    return value;
}

Then use it like this:

- const [ width, height ] = useWindowSize();
+ const [ width, height ] = useSsrCompatible(useWindowSize(), [0,0]);

which makes Next.js' SSR happy.

Definitely not ideal and makes sense not to include in the library. Although others may find this useful when needing to accomplish this, so glad there is an issue to track the question.

Glavin001 avatar Nov 21 '21 21:11 Glavin001

That's very good! I like it a lot.

Ralle avatar Nov 22 '21 09:11 Ralle