Skip to content

Commit e7b9c16

Browse files
authored
Fix RAC Modal in SSR (#4998)
1 parent 30f1728 commit e7b9c16

File tree

2 files changed

+58
-2
lines changed

2 files changed

+58
-2
lines changed

packages/react-aria-components/src/Modal.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
* governing permissions and limitations under the License.
1111
*/
1212

13-
import {AriaModalOverlayProps, DismissButton, Overlay, useModalOverlay} from 'react-aria';
13+
import {AriaModalOverlayProps, DismissButton, Overlay, useIsSSR, useModalOverlay} from 'react-aria';
1414
import {ContextValue, forwardRefType, RenderProps, SlotProps, useContextProps, useEnterAnimation, useExitAnimation, useRenderProps} from './utils';
1515
import {DOMAttributes} from '@react-types/shared';
1616
import {filterDOMProps, mergeProps, mergeRefs, useObjectRef, useViewportSize} from '@react-aria/utils';
@@ -106,8 +106,9 @@ function ModalOverlayWithForwardRef(props: ModalOverlayProps, ref: ForwardedRef<
106106
let isOverlayExiting = useExitAnimation(objectRef, state.isOpen);
107107
let isModalExiting = useExitAnimation(modalRef, state.isOpen);
108108
let isExiting = isOverlayExiting || isModalExiting;
109+
let isSSR = useIsSSR();
109110

110-
if (!state.isOpen && !isExiting) {
111+
if ((!state.isOpen && !isExiting) || isSSR) {
111112
return null;
112113
}
113114

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright 2023 Adobe. All rights reserved.
3+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License. You may obtain a copy
5+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
6+
*
7+
* Unless required by applicable law or agreed to in writing, software distributed under
8+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9+
* OF ANY KIND, either express or implied. See the License for the specific language
10+
* governing permissions and limitations under the License.
11+
*/
12+
13+
import {screen, testSSR} from '@react-spectrum/test-utils';
14+
15+
describe('Dialog SSR', function () {
16+
it('should render without errors', async function () {
17+
await testSSR(__filename, `
18+
import {Button, Dialog, DialogTrigger, Heading, Input, Label, Modal, TextField} from '../';
19+
20+
<React.StrictMode>
21+
<DialogTrigger isOpen>
22+
<Button>Sign up…</Button>
23+
<Modal>
24+
<Dialog>
25+
{({ close }) => (
26+
<form>
27+
<Heading>Sign up</Heading>
28+
<TextField autoFocus>
29+
<Label>First Name:</Label>
30+
<Input />
31+
</TextField>
32+
<TextField>
33+
<Label>Last Name:</Label>
34+
<Input />
35+
</TextField>
36+
<Button onPress={close}>
37+
Submit
38+
</Button>
39+
</form>
40+
)}
41+
</Dialog>
42+
</Modal>
43+
</DialogTrigger>
44+
</React.StrictMode>
45+
`, () => {
46+
// Assert that server rendered stuff into the HTML.
47+
let dialog = screen.queryByRole('dialog');
48+
expect(dialog).toBeNull();
49+
});
50+
51+
// Assert that hydrated UI matches what we expect.
52+
let dialog = screen.getByRole('dialog');
53+
expect(dialog).toBeInTheDocument();
54+
});
55+
});

0 commit comments

Comments
 (0)