Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
8903f25
Remove call to withLegacyGetProps
vmarta Jul 3, 2025
9f4bef1
Debugging
vmarta Jul 4, 2025
08d7e0a
Revert change to AppConfig
vmarta Jul 4, 2025
a7cbff4
Add todos
vmarta Jul 4, 2025
970b97e
Try commenting out the problematic Chakra hooks
vmarta Jul 10, 2025
65fabf6
Revert "Try commenting out the problematic Chakra hooks"
vmarta Jul 10, 2025
669f2eb
Create a safe version of useDisclosure
vmarta Jul 10, 2025
6efdffe
Fix react-ssr-prepass lack of no-op for useInsertionEffect
vmarta Jul 10, 2025
5118f28
Don't render Portal on server side
vmarta Jul 10, 2025
f5fbbe6
Show header again
vmarta Jul 10, 2025
abbf7c8
Revert "Create a safe version of useDisclosure"
vmarta Jul 10, 2025
efd2e7d
This Portal is already rendered only on client side
vmarta Jul 10, 2025
95d652a
Render nothing on server side
vmarta Jul 10, 2025
2e1dc0e
Merge branch 'feature/chakra-ui-upgrade-v3' into vm/ssr-regression-fix
vmarta Jul 10, 2025
d708825
Clean up code
vmarta Jul 11, 2025
ac39e44
Remove unnecessary category check
vmarta Jul 11, 2025
ec7be9b
Add comment
vmarta Jul 11, 2025
befb148
Update changelog files
vmarta Jul 11, 2025
c5e4cff
Merge branch 'feature/chakra-ui-upgrade-v3' into vm/ssr-regression-fix
vmarta Jul 11, 2025
aa8ffe5
Existing tests are now passing again
vmarta Jul 11, 2025
dc4d9c4
Revert change to the skeleton
vmarta Jul 14, 2025
d71e984
Add a comment closer to the prepass call
vmarta Jul 14, 2025
70ae4a2
Create a safe version of Portal
vmarta Jul 14, 2025
4b7c5e9
Update changelog files
vmarta Jul 14, 2025
99e90a9
Add comments to clarify
vmarta Jul 14, 2025
4409e57
Check first whether useInsertionEffect is defined or not
vmarta Jul 14, 2025
7f73b89
Add more tests to pass test coverage
vmarta Jul 14, 2025
e1236fb
Add comment
vmarta Jul 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/extension-chakra-storefront/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
- Send PWA Kit events to Data Cloud [#318] (https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2229)
- Fix dependencies vulnerabilities [#2338](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2338)
- Fix accessibility issues [#2375](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2375)
- Create a safe version of `<Portal>` that won't break the SSR rendering [#2785](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2785)

For historical changelog, look for the original `retail-react-app` in the [release notes](https://github.com/SalesforceCommerceCloud/pwa-kit/releases).
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@

import React, {useState, useRef} from 'react'
import PropTypes from 'prop-types'
import {Dialog, Portal, Flex, Box, VStack, useBreakpointValue} from '@chakra-ui/react'
import {Dialog, Flex, Box, VStack, useBreakpointValue} from '@chakra-ui/react'
import {keepPreviousData} from '@tanstack/react-query'
import ProductView from '../../components/product-view'
import {useProductViewModal} from '../../hooks/use-product-view-modal'
import SafePortal from '../safe-portal'
import {useProducts} from '@salesforce/commerce-sdk-react'
import ImageGallery, {Skeleton as ImageGallerySkeleton} from '../../components/image-gallery'
import {useDerivedProduct} from '../../hooks'
Expand Down Expand Up @@ -62,7 +63,7 @@ const BundleProductViewModal = ({product: bundle, isOpen, onClose, updateCart, .
size="4xl"
closeOnInteractOutside={false}
>
<Portal>
<SafePortal>
<Dialog.Backdrop />
<Dialog.Positioner>
<Dialog.Content data-testid="product-view-modal" aria-label={label}>
Expand Down Expand Up @@ -170,7 +171,7 @@ const BundleProductViewModal = ({product: bundle, isOpen, onClose, updateCart, .
</Dialog.Body>
</Dialog.Content>
</Dialog.Positioner>
</Portal>
</SafePortal>
</Dialog.Root>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@

import React from 'react'
import PropTypes from 'prop-types'
import {Dialog, Portal, CloseButton} from '@chakra-ui/react'
import {Dialog, CloseButton} from '@chakra-ui/react'
import ProductView from '../../components/product-view'
import {useProductViewModal} from '../../hooks/use-product-view-modal'
import SafePortal from '../safe-portal'
import {useIntl} from 'react-intl'

/**
Expand All @@ -34,7 +35,7 @@ const ProductViewModal = ({product, isOpen, onClose, ...props}) => {
size="xl"
closeOnInteractOutside={false}
>
<Portal>
<SafePortal>
<Dialog.Backdrop />
<Dialog.Positioner>
<Dialog.Content data-testid="product-view-modal" aria-label={label}>
Expand All @@ -53,7 +54,7 @@ const ProductViewModal = ({product, isOpen, onClose, ...props}) => {
</Dialog.CloseTrigger>
</Dialog.Content>
</Dialog.Positioner>
</Portal>
</SafePortal>
</Dialog.Root>
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright (c) 2021, salesforce.com, inc.
* All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/
import React, {useEffect, useState} from 'react'
import {Portal} from '@chakra-ui/react'

/**
* A safe version of Portal that only renders on the client side.
* This prevents SSR issues where Portal tries to access the DOM during server rendering.
* (Portal would call Ark UI's useEnvironmentContext hook, which tries to access `document` that doesn't exist on the server)
*
* @param {Object} props - Props to pass to the Portal component
* @returns {React.Element|null} Portal component or null if on server
*/
export const SafePortal = (props) => {
const [isClient, setIsClient] = useState(false)

useEffect(() => {
// This effect only runs on the client side
// And with it, we avoid any hydration mismatch error
setIsClient(true)
}, [])

// Don't render anything on the server or before hydration
if (!isClient) {
return null
}

return <Portal {...props} />
}

export default SafePortal
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
*/
import React from 'react'
import PropTypes from 'prop-types'
import {Toaster as ChakraToaster, Portal, Stack, Toast, createToaster} from '@chakra-ui/react'
import {Toaster as ChakraToaster, Stack, Toast, createToaster} from '@chakra-ui/react'
import SafePortal from '../safe-portal'

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

export default function Toaster({toaster}) {
return (
<Portal>
<SafePortal>
<ChakraToaster toaster={toaster} insetInline={{mdDown: '4'}}>
{(toast) => (
<Toast.Root width={{md: 'sm'}}>
Expand All @@ -33,7 +34,7 @@ export default function Toaster({toaster}) {
</Toast.Root>
)}
</ChakraToaster>
</Portal>
</SafePortal>
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import {
Dialog,
Flex,
Heading,
Portal,
Text,
Stack,
useBreakpointValue
Expand All @@ -29,6 +28,7 @@ import {findImageGroupBy} from '../utils/image-groups-utils'
import {getPriceData, getDisplayVariationValues} from '../utils/product-utils'
import {EINSTEIN_RECOMMENDERS} from '../constants'
import DisplayPrice from '../components/display-price'
import SafePortal from '../components/safe-portal'

/**
* This is the context for managing the AddToCartModal.
Expand Down Expand Up @@ -84,7 +84,7 @@ export const AddToCartModal = () => {
scrollBehavior="inside"
placement="center"
>
<Portal>
<SafePortal>
<Dialog.Backdrop />
<Dialog.Positioner>
<Dialog.Content
Expand Down Expand Up @@ -422,7 +422,7 @@ export const AddToCartModal = () => {
</Dialog.CloseTrigger>
</Dialog.Content>
</Dialog.Positioner>
</Portal>
</SafePortal>
</Dialog.Root>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import React, {useEffect, useState} from 'react'
import PropTypes from 'prop-types'
import {defineMessage, useIntl} from 'react-intl'
import {useForm} from 'react-hook-form'
import {Dialog, Portal, CloseButton, useDisclosure} from '@chakra-ui/react'
import {Dialog, CloseButton, useDisclosure} from '@chakra-ui/react'
import {keepPreviousData} from '@tanstack/react-query'
import {
AuthHelpers,
Expand Down Expand Up @@ -40,6 +40,7 @@ import {isServer} from '../utils/utils'
import {isAbsoluteURL} from '../page-designer/utils'
import {useAppOrigin} from './use-app-origin'
import {useExtensionConfig} from './use-extension-config'
import SafePortal from '../components/safe-portal'

export const LOGIN_VIEW = 'login'
export const REGISTER_VIEW = 'register'
Expand Down Expand Up @@ -286,7 +287,7 @@ export const AuthModal = ({
data-testid="sf-auth-modal"
{...props}
>
<Portal>
<SafePortal>
<Dialog.Backdrop />
<Dialog.Positioner>
<Dialog.Content>
Expand Down Expand Up @@ -342,7 +343,7 @@ export const AuthModal = ({
</Dialog.Body>
</Dialog.Content>
</Dialog.Positioner>
</Portal>
</SafePortal>
</Dialog.Root>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@
*/
import React, {useEffect, useRef, useState} from 'react'
import PropTypes from 'prop-types'
import {Alert, Box, Button, Container, Dialog, Portal, Stack, Text} from '@chakra-ui/react'
import {Alert, Box, Button, Container, Dialog, Stack, Text} from '@chakra-ui/react'
import {useForm} from 'react-hook-form'
import {FormattedMessage, useIntl} from 'react-intl'
import {useCheckout} from '../../../pages/checkout/util/checkout-context'
import useLoginFields from '../../../components/forms/useLoginFields'
import {ToggleCard, ToggleCardEdit, ToggleCardSummary} from '../../../components/toggle-card'
import Field from '../../../components/field'
import SafePortal from '../../../components/safe-portal'
import {AlertIcon} from '../../../components/icons'
import LoginState from '../../../pages/checkout/partials/login-state'
import {AuthModal, EMAIL_VIEW, PASSWORD_VIEW, useAuthModal} from '../../../hooks/use-auth-modal'
Expand Down Expand Up @@ -274,7 +275,7 @@ const SignOutConfirmationDialog = ({isOpen, onConfirm, onClose}) => {
open={isOpen}
onOpenChange={(details) => !details.open && onClose()}
>
<Portal>
<SafePortal>
<Dialog.Backdrop />
<Dialog.Positioner>
<Dialog.Content>
Expand Down Expand Up @@ -313,7 +314,7 @@ const SignOutConfirmationDialog = ({isOpen, onConfirm, onClose}) => {
</Dialog.Footer>
</Dialog.Content>
</Dialog.Positioner>
</Portal>
</SafePortal>
</Dialog.Root>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ import {
Button,
Field,
Dialog,
Portal,
Drawer,
NativeSelect
} from '@chakra-ui/react'
Expand Down Expand Up @@ -60,6 +59,7 @@ import {
useExtensionConfig
} from '../../hooks'
import useToast from '../../hooks/use-toast'
import SafePortal from '../../components/safe-portal'
import useEinstein from '../../hooks/use-einstein'
import useActiveData from '../../hooks/use-active-data'
import useDataCloud from '../../hooks/use-datacloud'
Expand Down Expand Up @@ -483,7 +483,7 @@ const ProductList = (props) => {
/>
</Button>
</Dialog.Trigger>
<Portal>
<SafePortal>
<Dialog.Backdrop />
<Dialog.Positioner>
<Dialog.Content
Expand Down Expand Up @@ -590,7 +590,7 @@ const ProductList = (props) => {
</Dialog.Footer>
</Dialog.Content>
</Dialog.Positioner>
</Portal>
</SafePortal>
</Dialog.Root>
</Flex>
<Flex align="center">
Expand Down Expand Up @@ -727,7 +727,7 @@ const ProductList = (props) => {
placement="bottom"
size="sm"
>
<Portal>
<SafePortal>
<Drawer.Backdrop />
<Drawer.Positioner>
<Drawer.Content>
Expand Down Expand Up @@ -775,7 +775,7 @@ const ProductList = (props) => {
</Drawer.Body>
</Drawer.Content>
</Drawer.Positioner>
</Portal>
</SafePortal>
</Drawer.Root>
</Box>
)
Expand Down
2 changes: 2 additions & 0 deletions packages/pwa-kit-react-sdk/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
## v4.0.0-extensibility-preview.5 (May 06, 2025)
- SSR rendering: implement workaround for react-ssr-prepass to ignore React's useInsertionEffect [#2785](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2785)

## v4.0.0-extensibility-preview.4 (Feb 12, 2025)
- Replace `event-emitter` in favor of the native `EventTarget` [#2289](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2289)
- Call app extension's new methods `getRoutes` and `getRoutesAsync` [#2308](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2308)
Expand Down
10 changes: 10 additions & 0 deletions packages/pwa-kit-react-sdk/src/ssr/server/react-rendering.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,16 @@ import * as errors from '../universal/errors'
import logger from '../../utils/logger-instance'
import PerformanceTimer, {PERFORMANCE_MARKS} from '../../utils/performance'

// Workaround for react-ssr-prepass lack of no-op for the React hook `useInsertionEffect`.
// (see https://github.com/FormidableLabs/react-ssr-prepass/issues/84)
// - Why useInsertionEffect? Chakra v3 hooks (like useDisclosure, useBreakpointValue, useMediaQuery, useCallbackRef) rely on it for rendering optimization.
// * see https://github.com/chakra-ui/chakra-ui/blob/c83e7f5acff2751ee3722bf22d89a5fe581fd72c/packages/react/src/hooks/use-callback-ref.ts#L17-L19
// - Why no-op? useInsertionEffect is meant for client side only. So on the server, we wanted prepass to ignore it.
// - Is it safe? Yes, we're not breaking the original behaviour. As React doc explains, the hook is for client side only: https://react.dev/reference/react/useInsertionEffect
if (React.useInsertionEffect) {
React.useInsertionEffect = () => {} // no-op
}

const CWD = process.cwd()
const BUNDLES_PATH = path.resolve(CWD, 'build/loadable-stats.json')

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,11 @@ export const withReactQuery = (Wrapped, options = {}) => {

res.__performanceTimer.mark(PERFORMANCE_MARKS.reactQueryPrerender, 'start')
// Use `ssrPrepass` to collect all uses of `useQuery`.
// NOTE: See a workaround in 'ssr/server/react-rendering.js' file that we had to implement,
// so that prepass would ignore React's useInsertionEffect hook.
await ssrPrepass(appJSX)
res.__performanceTimer.mark(PERFORMANCE_MARKS.reactQueryPrerender, 'end')

const queryCache = queryClient.getQueryCache()
const queries = queryCache.getAll().filter((q) => q.options.enabled !== false)
await Promise.all(
Expand Down
Loading
Loading