jaredLunde/react-hook

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

Opened this issue · 3 comments

Ralle commented

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_]);

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.

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.

Ralle commented

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