Skip to content

Commit

Permalink
Merge pull request #202 from oaknational/fix-oak-modal-blowing-up-in-ssr
Browse files Browse the repository at this point in the history
fix: `OakModal` blowing up when doing SSR
  • Loading branch information
carlmw authored Jun 19, 2024
2 parents c8e68c2 + cb51ee2 commit 869515a
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 106 deletions.
16 changes: 13 additions & 3 deletions src/components/molecules/OakModal/OakModal.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,26 @@ jest.mock("react-dom", () => {
});

describe(OakModal, () => {
it("matches snapshot", () => {
it("does not render until mounted on the client", () => {
const tree = create(
<OakThemeProvider theme={oakDefaultTheme}>
<OakModal isOpen onClose={() => {}} footerSlot="Modal footer">
Modal content
</OakModal>
</OakThemeProvider>,
).toJSON();
);

expect(tree.toJSON()).toBeNull();
});

it("matches snapshot when mounted", async () => {
const result = renderWithTheme(
<OakModal isOpen onClose={() => {}}>
Modal content
</OakModal>,
);

expect(tree).toMatchSnapshot();
expect(result.container).toMatchSnapshot();
});

it("calls onClose when the close button is clicked", () => {
Expand Down
16 changes: 14 additions & 2 deletions src/components/molecules/OakModal/OakModal.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, {
HTMLAttributes,
ReactNode,
useEffect,
useLayoutEffect,
useRef,
useState,
Expand Down Expand Up @@ -79,7 +80,7 @@ const logoSrc = `https://${process.env.NEXT_PUBLIC_OAK_ASSETS_HOST}/${process.en
export const OakModal = ({
children,
footerSlot,
domContainer = document.body,
domContainer,
isOpen,
onClose,
...rest
Expand Down Expand Up @@ -109,6 +110,17 @@ export const OakModal = ({
};
}, [canaryElement]);

// `createPortal` is not supported in SSR so we can only render when mounted on the client
const [isMounted, setIsMounted] = useState(false);

useEffect(() => {
setIsMounted(true);
}, []);

if (!isMounted) {
return null;
}

return createPortal(
<Transition
in={isOpen}
Expand Down Expand Up @@ -193,6 +205,6 @@ export const OakModal = ({
</FocusOn>
)}
</Transition>,
domContainer,
domContainer ?? document.body,
);
};
148 changes: 47 additions & 101 deletions src/components/molecules/OakModal/__snapshots__/OakModal.test.tsx.snap
Original file line number Diff line number Diff line change
@@ -1,23 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`OakModal matches snapshot 1`] = `
[
<div
data-focus-guard={true}
style={
{
"height": "0px",
"left": "1px",
"overflow": "hidden",
"padding": 0,
"position": "fixed",
"top": "1px",
"width": "1px",
}
}
tabIndex={-1}
/>,
.c0 {
exports[`OakModal matches snapshot when mounted 1`] = `
.c0 {
position: fixed;
inset: 0rem;
background: #222222;
Expand Down Expand Up @@ -193,16 +177,21 @@ exports[`OakModal matches snapshot 1`] = `
letter-spacing: 0.0115rem;
}
.c8 {
object-fit: contain;
}
.c20 {
-webkit-filter: invert(10%) sepia(1%) saturate(236%) hue-rotate(314deg) brightness(95%) contrast(91%);
filter: invert(10%) sepia(1%) saturate(236%) hue-rotate(314deg) brightness(95%) contrast(91%);
object-fit: contain;
}
.c8 {
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCA2NCA2NCI+PHBhdGggZmlsbD0iIzIyMiIgZD0iTTI4Ljc3OSAxOS4xNzZhMjcuMTkxIDI3LjE5MSAwIDAgMC0zLjggMS42IDE2LjcgMTYuNyAwIDAgMC03LjEgOC40YzAgLjEtLjEuMi0uMS4zLS43IDIuNC0uNiAyIDEuMyAyLjMgMS45LjMgMSAuNSAxIDEuMy0uMSA4LjggNC4xIDE1LjEgMTEuNCAxOS42YTEuNSAxLjUgMCAwIDAgMS43LjJjNS43LTIuNiA5LjMtNyAxMC4zLTEzLjJhMSAxIDAgMCAxIDEtMWwzLS4yYy44IDAgMS4zLjIgMS4yIDEuMmExNy45IDE3LjkgMCAwIDEtMy4yIDkuMiAyMy43IDIzLjcgMCAwIDEtMTAuOSA5LjEgNS40MDEgNS40MDEgMCAwIDEtNC41LS4yIDI2LjI5OCAyNi4yOTggMCAwIDEtOC41LTYuNiAyNS45IDI1LjkgMCAwIDEtNi40LTE0LjRjMC0uNi0uMi0uNy0uOC0uOC0yLjUtLjQtMi41LS4xLTIuMy0yLjlhMTkuMyAxOS4zIDAgMCAxIDEwLjgtMTYuNiAzOC45OTkgMzguOTk5IDAgMCAxIDUuNy0yLjEgMi4xIDIuMSAwIDAgMCAuOS0xLjMgMTQuMSAxNC4xIDAgMCAxIDMuNS02LjNsLjMtLjNjMS45LTIgMi42LTIgNC4zLjJsLjQuNWMxLjEgMS4xIDEgMS41LS4xIDIuNmExMS45IDExLjkgMCAwIDAtMy4yIDQuNCAxNi45IDE2LjkgMCAwIDEgNy41IDIuM2M1LjcgMy41IDkuMiA4LjMgOS45IDE1IC4wMTYuOTAxLS4wMTcgMS44MDItLjEgMi43IDAgLjgtLjYgMS0xLjIgMS4yYTE2LjEgMTYuMSAwIDAgMS0xMS0uNyAxNy45MDEgMTcuOTAxIDAgMCAxLTEwLjktMTMuNiA5Ljc5NiA5Ljc5NiAwIDAgMS0uMS0xLjlabTE4LjEgMTIuMmMuNC01LjUtNi45LTEyLjYtMTMtMTIuMS41IDYuNSA3LjYgMTIuOCAxMyAxMi4xWiIgb3BhY2l0eT0iLjEiLz48L3N2Zz4=);
background-color: #e7f6f5;
background-size: 4rem;
background-position: center;
background-repeat: no-repeat;
object-fit: contain;
}
.c12 {
background: none;
color: inherit;
Expand Down Expand Up @@ -281,140 +270,97 @@ exports[`OakModal matches snapshot 1`] = `
transform: translateX(0);
}
<div
data-focus-lock-disabled="disabled"
onBlur={[Function]}
onFocus={[Function]}
onScrollCapture={[Function]}
onTouchMoveCapture={[Function]}
onWheelCapture={[Function]}
<div>
<div
aria-hidden="true"
data-focus-guard="true"
data-focus-on-hidden="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
<div
data-focus-lock-disabled="false"
>
<div
className="c0 c1"
class="c0 c1"
/>
<div
className="c2 c3 c4"
class="c2 c3 c4"
role="dialog"
>
<div
className="c5 c6"
class="c5 c6"
>
<div
className="c7"
class="c7"
>
<img
alt=""
className="c8"
class="c8"
data-nimg="fill"
decoding="async"
loading="lazy"
onError={[Function]}
onLoad={[Function]}
src="https://res.cloudinary.com/mock-cloudinary-cloud/image/upload/logo-mark.svg"
style={
{
"bottom": 0,
"color": "transparent",
"height": "100%",
"left": 0,
"objectFit": undefined,
"objectPosition": undefined,
"position": "absolute",
"right": 0,
"top": 0,
"width": "100%",
}
}
style="position: absolute; height: 100%; width: 100%; left: 0px; top: 0px; right: 0px; bottom: 0px; color: transparent;"
/>
</div>
<div
className="c9 c10 c11"
class="c9 c10 c11"
>
<button
aria-label="Close"
className="c12 c13"
onClick={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
class="c12 c13"
>
<div
className="c14 c15"
class="c14 c15"
>
<div
className="c16 c17 icon-container"
class="c16 c17 icon-container"
>
<div
className="c18 shadow"
class="c18 shadow"
/>
<div
className="c19"
class="c19"
data-icon-for="button"
>
<img
alt="cross"
className="c20"
class="c20"
data-nimg="fill"
decoding="async"
loading="lazy"
onError={[Function]}
onLoad={[Function]}
src="https://res.cloudinary.com/mock-cloudinary-cloud/image/upload/v1699895179/icons/xigimrbivcaxt4omxamp.svg"
style={
{
"bottom": 0,
"color": "transparent",
"height": "100%",
"left": 0,
"objectFit": undefined,
"objectPosition": undefined,
"position": "absolute",
"right": 0,
"top": 0,
"width": "100%",
}
}
style="position: absolute; height: 100%; width: 100%; left: 0px; top: 0px; right: 0px; bottom: 0px; color: transparent;"
/>
</div>
</div>
<span
className="c21"
class="c21"
/>
</div>
</button>
</div>
</div>
<div
data-autofocus-inside={true}
style={
{
"display": "contents",
}
}
data-autofocus-inside="true"
style="display: contents;"
>
<div
className="c22 c23"
class="c22 c23"
>
<div />
Modal content
</div>
Modal footer
</div>
</div>
</div>,
</div>
<div
data-focus-guard={true}
style={
{
"height": "0px",
"left": "1px",
"overflow": "hidden",
"padding": 0,
"position": "fixed",
"top": "1px",
"width": "1px",
}
}
tabIndex={-1}
/>,
]
aria-hidden="true"
data-focus-guard="true"
data-focus-on-hidden="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
</div>
`;

0 comments on commit 869515a

Please sign in to comment.