Skip to content

Commit 094fc16

Browse files
authored
[Chakra v3] data fetching during SSR rendering now works again (@W-18822712@) (#2785)
* Remove call to withLegacyGetProps * Debugging * Revert change to AppConfig * Add todos * Try commenting out the problematic Chakra hooks Including those components that also call `useDisclosure`. * Revert "Try commenting out the problematic Chakra hooks" This reverts commit 970b97e. * Create a safe version of useDisclosure * Fix react-ssr-prepass lack of no-op for useInsertionEffect * Don't render Portal on server side * Show header again * Revert "Create a safe version of useDisclosure" This reverts commit 669f2eb. * This Portal is already rendered only on client side * Render nothing on server side * Clean up code * Remove unnecessary category check * Add comment * Update changelog files * Existing tests are now passing again * Revert change to the skeleton * Add a comment closer to the prepass call The setting of useInsertionEffect to a no-op had to be done in another file. * Create a safe version of Portal * Update changelog files * Add comments to clarify * Check first whether useInsertionEffect is defined or not * Add more tests to pass test coverage I didn't want to add test to react-rendering.test.js because my code change isn't much for testing. So I decided to add tests for another file instead. * Add comment
1 parent 8ed8c86 commit 094fc16

File tree

9 files changed

+64
-23
lines changed

9 files changed

+64
-23
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@
44
- Send PWA Kit events to Data Cloud [#318] (https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2229)
55
- Fix dependencies vulnerabilities [#2338](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2338)
66
- Fix accessibility issues [#2375](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2375)
7+
- Create a safe version of `<Portal>` that won't break the SSR rendering [#2785](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2785)
78

89
For historical changelog, look for the original `retail-react-app` in the [release notes](https://github.com/SalesforceCommerceCloud/pwa-kit/releases).

src/components/product-view-modal/bundle.jsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@
77

88
import React, {useState, useRef} from 'react'
99
import PropTypes from 'prop-types'
10-
import {Dialog, Portal, Flex, Box, VStack, useBreakpointValue} from '@chakra-ui/react'
10+
import {Dialog, Flex, Box, VStack, useBreakpointValue} from '@chakra-ui/react'
1111
import {keepPreviousData} from '@tanstack/react-query'
1212
import ProductView from '../../components/product-view'
1313
import {useProductViewModal} from '../../hooks/use-product-view-modal'
14+
import SafePortal from '../safe-portal'
1415
import {useProducts} from '@salesforce/commerce-sdk-react'
1516
import ImageGallery, {Skeleton as ImageGallerySkeleton} from '../../components/image-gallery'
1617
import {useDerivedProduct} from '../../hooks'
@@ -62,7 +63,7 @@ const BundleProductViewModal = ({product: bundle, isOpen, onClose, updateCart, .
6263
size="4xl"
6364
closeOnInteractOutside={false}
6465
>
65-
<Portal>
66+
<SafePortal>
6667
<Dialog.Backdrop />
6768
<Dialog.Positioner>
6869
<Dialog.Content data-testid="product-view-modal" aria-label={label}>
@@ -170,7 +171,7 @@ const BundleProductViewModal = ({product: bundle, isOpen, onClose, updateCart, .
170171
</Dialog.Body>
171172
</Dialog.Content>
172173
</Dialog.Positioner>
173-
</Portal>
174+
</SafePortal>
174175
</Dialog.Root>
175176
)
176177
}

src/components/product-view-modal/index.jsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@
77

88
import React from 'react'
99
import PropTypes from 'prop-types'
10-
import {Dialog, Portal, CloseButton} from '@chakra-ui/react'
10+
import {Dialog, CloseButton} from '@chakra-ui/react'
1111
import ProductView from '../../components/product-view'
1212
import {useProductViewModal} from '../../hooks/use-product-view-modal'
13+
import SafePortal from '../safe-portal'
1314
import {useIntl} from 'react-intl'
1415

1516
/**
@@ -34,7 +35,7 @@ const ProductViewModal = ({product, isOpen, onClose, ...props}) => {
3435
size="xl"
3536
closeOnInteractOutside={false}
3637
>
37-
<Portal>
38+
<SafePortal>
3839
<Dialog.Backdrop />
3940
<Dialog.Positioner>
4041
<Dialog.Content data-testid="product-view-modal" aria-label={label}>
@@ -53,7 +54,7 @@ const ProductViewModal = ({product, isOpen, onClose, ...props}) => {
5354
</Dialog.CloseTrigger>
5455
</Dialog.Content>
5556
</Dialog.Positioner>
56-
</Portal>
57+
</SafePortal>
5758
</Dialog.Root>
5859
)
5960
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright (c) 2021, salesforce.com, inc.
3+
* All rights reserved.
4+
* SPDX-License-Identifier: BSD-3-Clause
5+
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
6+
*/
7+
import React, {useEffect, useState} from 'react'
8+
import {Portal} from '@chakra-ui/react'
9+
10+
/**
11+
* A safe version of Portal that only renders on the client side.
12+
* This prevents SSR issues where Portal tries to access the DOM during server rendering.
13+
* (Portal would call Ark UI's useEnvironmentContext hook, which tries to access `document` that doesn't exist on the server)
14+
*
15+
* @param {Object} props - Props to pass to the Portal component
16+
* @returns {React.Element|null} Portal component or null if on server
17+
*/
18+
export const SafePortal = (props) => {
19+
const [isClient, setIsClient] = useState(false)
20+
21+
useEffect(() => {
22+
// This effect only runs on the client side
23+
// And with it, we avoid any hydration mismatch error
24+
setIsClient(true)
25+
}, [])
26+
27+
// Don't render anything on the server or before hydration
28+
if (!isClient) {
29+
return null
30+
}
31+
32+
return <Portal {...props} />
33+
}
34+
35+
export default SafePortal

src/components/toaster/index.jsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
*/
77
import React from 'react'
88
import PropTypes from 'prop-types'
9-
import {Toaster as ChakraToaster, Portal, Stack, Toast, createToaster} from '@chakra-ui/react'
9+
import {Toaster as ChakraToaster, Stack, Toast, createToaster} from '@chakra-ui/react'
10+
import SafePortal from '../safe-portal'
1011

1112
// A toaster is a shared global instance that can be used to create and manage toasts.
1213
export const toaster = createToaster({
@@ -15,7 +16,7 @@ export const toaster = createToaster({
1516

1617
export default function Toaster({toaster}) {
1718
return (
18-
<Portal>
19+
<SafePortal>
1920
<ChakraToaster toaster={toaster} insetInline={{mdDown: '4'}}>
2021
{(toast) => (
2122
<Toast.Root width={{md: 'sm'}}>
@@ -33,7 +34,7 @@ export default function Toaster({toaster}) {
3334
</Toast.Root>
3435
)}
3536
</ChakraToaster>
36-
</Portal>
37+
</SafePortal>
3738
)
3839
}
3940

src/hooks/use-add-to-cart-modal.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import {
1616
Dialog,
1717
Flex,
1818
Heading,
19-
Portal,
2019
Text,
2120
Stack,
2221
useBreakpointValue
@@ -29,6 +28,7 @@ import {findImageGroupBy} from '../utils/image-groups-utils'
2928
import {getPriceData, getDisplayVariationValues} from '../utils/product-utils'
3029
import {EINSTEIN_RECOMMENDERS} from '../constants'
3130
import DisplayPrice from '../components/display-price'
31+
import SafePortal from '../components/safe-portal'
3232

3333
/**
3434
* This is the context for managing the AddToCartModal.
@@ -93,7 +93,7 @@ export const AddToCartModal = () => {
9393
scrollBehavior="inside"
9494
placement="center"
9595
>
96-
<Portal>
96+
<SafePortal>
9797
<Dialog.Backdrop />
9898
<Dialog.Positioner>
9999
<Dialog.Content
@@ -425,7 +425,7 @@ export const AddToCartModal = () => {
425425
</Dialog.CloseTrigger>
426426
</Dialog.Content>
427427
</Dialog.Positioner>
428-
</Portal>
428+
</SafePortal>
429429
</Dialog.Root>
430430
)
431431
}

src/hooks/use-auth-modal.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import React, {useEffect, useState} from 'react'
88
import PropTypes from 'prop-types'
99
import {defineMessage, useIntl} from 'react-intl'
1010
import {useForm} from 'react-hook-form'
11-
import {Dialog, Portal, CloseButton, useDisclosure} from '@chakra-ui/react'
11+
import {Dialog, CloseButton, useDisclosure} from '@chakra-ui/react'
1212
import {keepPreviousData} from '@tanstack/react-query'
1313
import {
1414
AuthHelpers,
@@ -39,6 +39,7 @@ import {isServer} from '../utils/utils'
3939
import {isAbsoluteURL} from '../page-designer/utils'
4040
import {useAppOrigin} from './use-app-origin'
4141
import {useExtensionConfig} from './use-extension-config'
42+
import SafePortal from '../components/safe-portal'
4243

4344
export const LOGIN_VIEW = 'login'
4445
export const REGISTER_VIEW = 'register'
@@ -284,7 +285,7 @@ export const AuthModal = ({
284285
data-testid="sf-auth-modal"
285286
{...props}
286287
>
287-
<Portal>
288+
<SafePortal>
288289
<Dialog.Backdrop />
289290
<Dialog.Positioner>
290291
<Dialog.Content>
@@ -340,7 +341,7 @@ export const AuthModal = ({
340341
</Dialog.Body>
341342
</Dialog.Content>
342343
</Dialog.Positioner>
343-
</Portal>
344+
</SafePortal>
344345
</Dialog.Root>
345346
)
346347
}

src/pages/checkout/partials/contact-info.jsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@
66
*/
77
import React, {useEffect, useRef, useState} from 'react'
88
import PropTypes from 'prop-types'
9-
import {Alert, Box, Button, Container, Dialog, Portal, Stack, Text} from '@chakra-ui/react'
9+
import {Alert, Box, Button, Container, Dialog, Stack, Text} from '@chakra-ui/react'
1010
import {useForm} from 'react-hook-form'
1111
import {FormattedMessage, useIntl} from 'react-intl'
1212
import {useCheckout} from '../../../pages/checkout/util/checkout-context'
1313
import useLoginFields from '../../../components/forms/useLoginFields'
1414
import {ToggleCard, ToggleCardEdit, ToggleCardSummary} from '../../../components/toggle-card'
1515
import Field from '../../../components/field'
16+
import SafePortal from '../../../components/safe-portal'
1617
import {AlertIcon} from '../../../components/icons'
1718
import LoginState from '../../../pages/checkout/partials/login-state'
1819
import {AuthModal, EMAIL_VIEW, PASSWORD_VIEW, useAuthModal} from '../../../hooks/use-auth-modal'
@@ -275,7 +276,7 @@ const SignOutConfirmationDialog = ({isOpen, onConfirm, onClose}) => {
275276
open={isOpen}
276277
onOpenChange={(details) => !details.open && onClose()}
277278
>
278-
<Portal>
279+
<SafePortal>
279280
<Dialog.Backdrop />
280281
<Dialog.Positioner>
281282
<Dialog.Content>
@@ -314,7 +315,7 @@ const SignOutConfirmationDialog = ({isOpen, onConfirm, onClose}) => {
314315
</Dialog.Footer>
315316
</Dialog.Content>
316317
</Dialog.Positioner>
317-
</Portal>
318+
</SafePortal>
318319
</Dialog.Root>
319320
)
320321
}

src/pages/product-list/index.jsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ import {
3030
Button,
3131
Field,
3232
Dialog,
33-
Portal,
3433
Drawer,
3534
NativeSelect
3635
} from '@chakra-ui/react'
@@ -54,6 +53,7 @@ import {FilterIcon, ChevronDownIcon} from '../../components/icons'
5453
// Hooks
5554
import {usePageUrls, useSortUrls, useSearchParams, useExtensionConfig} from '../../hooks'
5655
import useToast from '../../hooks/use-toast'
56+
import SafePortal from '../../components/safe-portal'
5757
import useEinstein from '../../hooks/use-einstein'
5858
import useActiveData from '../../hooks/use-active-data'
5959
import useDataCloud from '../../hooks/use-datacloud'
@@ -477,7 +477,7 @@ const ProductList = (props) => {
477477
/>
478478
</Button>
479479
</Dialog.Trigger>
480-
<Portal>
480+
<SafePortal>
481481
<Dialog.Backdrop />
482482
<Dialog.Positioner>
483483
<Dialog.Content
@@ -584,7 +584,7 @@ const ProductList = (props) => {
584584
</Dialog.Footer>
585585
</Dialog.Content>
586586
</Dialog.Positioner>
587-
</Portal>
587+
</SafePortal>
588588
</Dialog.Root>
589589
</Flex>
590590
<Flex align="center">
@@ -721,7 +721,7 @@ const ProductList = (props) => {
721721
placement="bottom"
722722
size="sm"
723723
>
724-
<Portal>
724+
<SafePortal>
725725
<Drawer.Backdrop />
726726
<Drawer.Positioner>
727727
<Drawer.Content>
@@ -769,7 +769,7 @@ const ProductList = (props) => {
769769
</Drawer.Body>
770770
</Drawer.Content>
771771
</Drawer.Positioner>
772-
</Portal>
772+
</SafePortal>
773773
</Drawer.Root>
774774
</Box>
775775
)

0 commit comments

Comments
 (0)