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?.containshere: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
useIsSSRcheck 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
isOpenproperty 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
ariaHideOutsidehelper. This error is currently caused by theOverlaycomponent and theportalContainer, as you can see here : https://github.com/adobe/react-spectrum/blob/86b9d3d0564244215a73c3bd18edad53078c4f3b/packages/%40react-aria/overlays/src/Overlay.tsx#L37 by defaultportalContaineris set tonullwhenisSSRis true. When we are wrapping our application inSSRProvider, the hookuseIsSSRalways returntrueat first, thenfalseafter passing theuseLayoutEffectinside theifcondition 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
SSRProviderhere, 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
portalContainerref onOverlaycomponent, by doing that, you avoid thenullset 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
refis set to a div element unattached to the main document with no children, soariaHideOutsidedoes essentially nothing. When the modal is mounted, it callssetRefwith its ref value, which causes the component to re-render, callinguseModalOverlaya 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.