react-spectrum: SSRProvider causes an error when conditionally rendering an Overlay (Next.js)
🐛 Bug Report
When wrapping the whole application with SSRProvider
and then conditionally rendering an Overlay
or OverlayContainer
component with the useOverlayTriggerState
state hook, an Unhandled Runtime Error is thrown.
Unhandled Runtime Error
TypeError: Cannot read properties of null (reading 'contains')
It doesn’t happen when the SSRProvider
is removed in _app.tsx
.
I’ve tried all sorts of different things like reimplementing a Modal in different ways with react-aria. I’ve followed the official documentation etc. It all seems to be tied to the SSRProvider
component somehow. I don’t understand the code well enough to find a solution myself.
🤔 Expected Behavior
I expect the SSRProvider
not to cause unhandled runtime errors when conditionally rendering react-aria components.
😯 Current Behavior
Conditionally rendering the Overlay
component from react-aria
causes an Unhandled Runtime Error.
Unhandled Runtime Error
TypeError: Cannot read properties of null (reading 'contains')
Call Stack
eval
node_modules/@react-aria/overlays/dist/module.js (1102:0)
Array.some
<anonymous>
MutationObserver.eval
node_modules/@react-aria/overlays/dist/module.js (1102:0)
💻 Code Sample
The code sample is a standard Nextjs setup.
Relevant files are components/modal.tsx
and pages/index.tsx
.
https://codesandbox.io/p/sandbox/react-aria-conditional-overlay-ssrprovider-7rvohm?file=%2Fpages%2Findex.tsx
🌍 Your Environment
Software | Version(s) |
---|---|
react-aria | 3.21.0 |
react-stately | 3.19.0 |
Browser | Chrome |
Operating System | Macbook Pro, Ventura 13.0.1 |
About this issue
- Original URL
- State: closed
- Created 2 years ago
- Reactions: 7
- Comments: 17 (2 by maintainers)
I confirmed this in our Next.js test app build too. You can see it if you open the ComboBox and check the console. Not seeing it in any other React Spectrum overlays interestingly.
We can fix this with a change to
node?.contains
here:https://github.com/adobe/react-spectrum/blob/168ca5a5cd53589bae6620fba1602e18240efc46/packages/%40react-aria/overlays/src/ariaHideOutside.ts#L97
but we probably need to figure out why that ref is null for a render in the first place.
I got to the bottom of this in #4297. This is actually the same issue as #3293. Due to the
useIsSSR
check here: https://github.com/adobe/react-spectrum/blob/a1efafd9e8bf893a6b433a3ccab60ff66295c302/packages/%40react-aria/overlays/src/Overlay.tsx#L37The popover is not rendered for an extra render cycle after the combobox state’s
isOpen
property becomes true. That causes the popover ref to be null and the crash here.The fix is to only perform an extra re-render during initial hydration, and not whenever any component mounts inside an SSR environment later on.
this is working fine for me:
I’m able to replicate the same error as well
Hello there,
I have also encountered this issue, I think that it might be interesting to avoid crash when a reference is null or undefined in
ariaHideOutside
helper. This error is currently caused by theOverlay
component and theportalContainer
, as you can see here : https://github.com/adobe/react-spectrum/blob/86b9d3d0564244215a73c3bd18edad53078c4f3b/packages/%40react-aria/overlays/src/Overlay.tsx#L37 by defaultportalContainer
is set tonull
whenisSSR
is true. When we are wrapping our application inSSRProvider
, the hookuseIsSSR
always returntrue
at first, thenfalse
after passing theuseLayoutEffect
inside theif
condition instead of being false since the beginning. That switch between true and false is for me the main issue behind all of this.I’m not enough aware about SSR constraints and the system used behind
SSRProvider
here, but can’t we avoid the state change in this hook due to rerender after setting the state ? I would have suggest to replace this line : https://github.com/adobe/react-spectrum/blob/86b9d3d0564244215a73c3bd18edad53078c4f3b/packages/%40react-aria/ssr/src/SSRProvider.tsx#L94 by something like :let [isSSR, setIsSSR] = useState(typeof window === 'undefined' && isInSSRContext);
but i’m pretty sure that this is not as easy as that 😛I currently have found a workaround since i’m using react-aria hooks, this is to set a
portalContainer
ref onOverlay
component, by doing that, you avoid thenull
set byisSSR
.In this case my workaround was to give the listBoxRef in there as popoverRef. Because I wanted it to use without a popover or something similiar.
`… let inputRef = React.useRef(null); let listBoxRef = React.useRef(null); let popoverRef = listBoxRef;
Here’s a dirty workaround that seems to work:
When the component first renders, the value of
ref
is set to a div element unattached to the main document with no children, soariaHideOutside
does essentially nothing. When the modal is mounted, it callssetRef
with its ref value, which causes the component to re-render, callinguseModalOverlay
a second time with the correct ref, so it works as expected.I get the same error in Remix
Seeing this issue as well when running a useComboBox example in Next.