react-spectrum
react-spectrum copied to clipboard
`useIsSSR()` hook always returns `true` and then `false` after hydration on mount
๐ Bug Report
I may have misunderstood the useIsSSR() documentation, but it isn't working the way I would expect it to.
๐ค Expected Behavior
The useIsSSR() hook should always return false after the app has hydrated on the client.
๐ฏ Current Behavior
Currently, the hook always returns true (meaning the app is server side rendering, or hydrating) before flipping to false every time it's mounted, even after hydration. I would expect it to always return false after hydration instead of true and then false.
๐ Possible Solution
No possible solution, but this line seems suspect to me. The isSSR state is always initially true, even if the app has already hydrated.
๐ฆ Context
My use case involves using useIsSSR() to create a hook that returns the device's pixel ratio. On the server and during hydration this hook returns a default value. After hydration it should always return window.devicePixelRatio.
import { useIsSSR } from '@react-aria/ssr';
function useDevicePixelRatio(defaultRatio = 1) {
let isSSROrHydration = useIsSSR();
return isSSROrHydration
? defaultRatio
: window.devicePixelRatio;
}
๐ป Code Sample
This example is a bit contrived since there isn't any server side rendering happening, but it still illustrates the potential issue.
https://codesandbox.io/s/tender-violet-kunchu?file=/src/App.js
๐ Your Environment
| Software | Version(s) |
|---|---|
| react-spectrum | @react-aria/[email protected] |
| Browser | Chrome |
| Operating System | MacOS |
๐งข Your Company/Team
SmugMug
This is intentional. It's required to avoid hydration mismatches between what's rendered on the server and client, or React will emit warnings. So the first render will always match what was rendered on the server, and then update to the client rendering afterward.
Hey @devongovett, thanks for responding. So just to be clear, if my app has already hydrated, and then I render a new component using useIsSSR(), it's expected that it should first return true and then false, instead of just returning false?
Ah I understand what you mean. We could potentially keep track of that I guess using some global flag, but not exactly sure how we'd know when hydration is complete for all components...
not exactly sure how we'd know when hydration is complete for all components...
Previously I had a useEffect() in my root <App> component that would update a HydrationContext when run, signaling that hydration had completed. This may be naive, but theoretically something similar could be done in SSRProvider.
I'm on React 17 currently, so I haven't put any thought into whether or not React 18 throws a wrench in that.
Yeah I think partial hydration/async rendering probably would make this harder.
Doing a little more digging into React 18's selective hydration, and I think for my current specific use case, using the new useSyncExternalStore hook (or the shim since I'm on React 17 at the moment) is a better solution than trying to track when hydration is complete myself.
I still think that the useIsSSR() hook seems a little flawed, but I don't have a good solution. Perhaps the useSyncExternalStore() hook will ultimately end up becoming the go-to for this sort of problem anyways.