diff --git a/assets/js/base/utils/render-frontend.js b/assets/js/base/utils/render-frontend.js
index 5e5edc721c3..ec83d91e43f 100644
--- a/assets/js/base/utils/render-frontend.js
+++ b/assets/js/base/utils/render-frontend.js
@@ -4,14 +4,14 @@
import { render, Suspense } from '@wordpress/element';
import BlockErrorBoundary from '@woocommerce/base-components/block-error-boundary';
-// Some blocks take care of rendering their inner blocks automatically. For example,
-// the empty cart. In those cases, we don't want to trigger the render function of
-// inner components on load. Instead, the wrapper block can trigger the event
-// `wc-blocks_render_blocks_frontend` to render its inner blocks.
+// Some blocks take care of rendering their inner blocks automatically. For
+// example, the empty cart. In those cases, we don't want to trigger the render
+// function of inner components on load. Instead, the wrapper block can trigger
+// the event `wc-blocks_render_blocks_frontend` to render its inner blocks.
const selectorsToSkipOnLoad = [ '.wp-block-woocommerce-cart' ];
-// Given an element and a list of wrappers, check if the element is inside at least
-// one of the wrappers.
+// Given an element and a list of wrappers, check if the element is inside at
+// least one of the wrappers.
const isElementInsideWrappers = ( el, wrappers ) => {
return Array.prototype.some.call(
wrappers,
@@ -20,84 +20,165 @@ const isElementInsideWrappers = ( el, wrappers ) => {
};
/**
- * Renders a block component in the place of a specified set of selectors.
+ * Renders a block component in each `containers` node.
*
* @param {Object} props Render props.
- * @param {Function} props.Block React component to use as a replacement.
- * @param {string} props.selector CSS selector to match the elements to replace.
- * @param {Function} [props.getProps] Function to generate the props object for the block.
- * @param {Function} [props.getErrorBoundaryProps] Function to generate the props object for the error boundary.
- * @param {Element} [props.wrapper] Element to query the selector inside. Defaults to the document body.
- * @param {Element[]} [props.wrappersToSkip] Don't render inner blocks of this parent.
+ * @param {Function} props.Block React component to use as a
+ * replacement.
+ * @param {NodeList} props.containers Containers to replace with
+ * the Block component.
+ * @param {Function} [props.getProps] Function to generate the
+ * props object for the block.
+ * @param {Function} [props.getErrorBoundaryProps] Function to generate the
+ * props object for the error
+ * boundary.
*/
-const renderBlockFrontend = ( {
+const renderBlockInContainers = ( {
Block,
- selector,
+ containers,
getProps = () => {},
getErrorBoundaryProps = () => {},
- wrapper = document.body,
- wrappersToSkip = [],
} ) => {
- const containers = wrapper.querySelectorAll( selector );
+ if ( containers.length === 0 ) {
+ return;
+ }
- if ( containers.length ) {
- // @todo Remove Suspense compatibility fix once WP 5.2 is no longer supported.
- // If Suspense is not available (WP 5.2), use a noop component instead.
- const noopComponent = ( { children } ) => {
- return <>{ children }>;
- };
- const SuspenseComponent = Suspense || noopComponent;
+ // @todo Remove Suspense compatibility fix once WP 5.2 is no longer supported.
+ // If Suspense is not available (WP 5.2), use a noop component instead.
+ const noopComponent = ( { children } ) => {
+ return <>{ children }>;
+ };
+ const SuspenseComponent = Suspense || noopComponent;
- // Use Array.forEach for IE11 compatibility.
- Array.prototype.forEach.call( containers, ( el, i ) => {
- if (
- wrappersToSkip.length > 0 &&
- isElementInsideWrappers( el, wrappersToSkip )
- ) {
- return;
- }
+ // Use Array.forEach for IE11 compatibility.
+ Array.prototype.forEach.call( containers, ( el, i ) => {
+ const props = getProps( el, i );
+ const errorBoundaryProps = getErrorBoundaryProps( el, i );
+ const attributes = {
+ ...el.dataset,
+ ...props.attributes,
+ };
+ el.classList.remove( 'is-loading' );
- const props = getProps( el, i );
- const errorBoundaryProps = getErrorBoundaryProps( el, i );
- const attributes = {
- ...el.dataset,
- ...props.attributes,
- };
- el.classList.remove( 'is-loading' );
+ render(
+
+ }
+ >
+
+
+ ,
+ el
+ );
+ } );
+};
- render(
-
- }
- >
-
-
- ,
- el
- );
+/**
+ * Renders the block frontend in the elements matched by the selector which are
+ * outside the wrapper elements.
+ *
+ * @param {Object} props Render props.
+ * @param {Function} props.Block React component to use as a
+ * replacement.
+ * @param {string} props.selector CSS selector to match the
+ * elements to replace.
+ * @param {Function} [props.getProps] Function to generate the
+ * props object for the block.
+ * @param {Function} [props.getErrorBoundaryProps] Function to generate the
+ * props object for the error
+ * boundary.
+ * @param {NodeList} props.wrappers All elements matched by the
+ * selector which are inside
+ * the wrapper will be ignored.
+ */
+const renderBlockOutsideWrappers = ( {
+ Block,
+ getProps,
+ getErrorBoundaryProps,
+ selector,
+ wrappers,
+} ) => {
+ const containers = document.body.querySelectorAll( selector );
+ // Filter out blocks inside the wrappers.
+ if ( wrappers.length > 0 ) {
+ Array.prototype.filter.call( containers, ( el ) => {
+ return ! isElementInsideWrappers( el, wrappers );
} );
}
+ renderBlockInContainers( {
+ Block,
+ containers,
+ getProps,
+ getErrorBoundaryProps,
+ } );
};
/**
- * Adds the event listeners necessary to render the block frontend.
+ * Renders the block frontend in the elements matched by the selector inside the
+ * wrapper element.
*
- * @param {Object} props Render props.
+ * @param {Object} props Render props.
+ * @param {Function} props.Block React component to use as a
+ * replacement.
+ * @param {string} props.selector CSS selector to match the
+ * elements to replace.
+ * @param {Function} [props.getProps] Function to generate the
+ * props object for the block.
+ * @param {Function} [props.getErrorBoundaryProps] Function to generate the
+ * props object for the error
+ * boundary.
+ * @param {Element} props.wrapper Wrapper element to query the
+ * selector inside.
+ */
+const renderBlockInsideWrapper = ( {
+ Block,
+ getProps,
+ getErrorBoundaryProps,
+ selector,
+ wrapper,
+} ) => {
+ const containers = wrapper.querySelectorAll( selector );
+ renderBlockInContainers( {
+ Block,
+ containers,
+ getProps,
+ getErrorBoundaryProps,
+ } );
+};
+
+/**
+ * Renders the block frontend on page load. If the block is contained inside a
+ * wrapper element that should be excluded from initial load, it adds the
+ * appropriate event listeners to render the block when the
+ * `blocks_render_blocks_frontend` event is triggered.
+ *
+ * @param {Object} props Render props.
+ * @param {Function} props.Block React component to use as a
+ * replacement.
+ * @param {string} props.selector CSS selector to match the
+ * elements to replace.
+ * @param {Function} [props.getProps] Function to generate the
+ * props object for the block.
+ * @param {Function} [props.getErrorBoundaryProps] Function to generate the
+ * props object for the error
+ * boundary.
*/
export const renderFrontend = ( props ) => {
+ // Some blocks might want to handle rendering inner blocks themselves. For
+ // example, the empty Cart template should render inner blocks when it
+ // becomes visible.
const wrappersToSkipOnLoad = document.body.querySelectorAll(
selectorsToSkipOnLoad.join( ',' )
);
- // Render on page load.
- renderBlockFrontend( {
+ renderBlockOutsideWrappers( {
...props,
- wrappersToSkip: wrappersToSkipOnLoad,
+ wrappers: wrappersToSkipOnLoad,
} );
- // Render wrappers inner blocks when the event `wc-blocks_render_blocks_frontend`
- // is triggered.
+ // For each wrapper, add an event listener to render the inner blocks when
+ // `wc-blocks_render_blocks_frontend` event is triggered.
Array.prototype.forEach.call( wrappersToSkipOnLoad, ( wrapper ) => {
- wrapper.addEventListener( 'wc-blocks_render_blocks_frontend', ( e ) => {
- renderBlockFrontend( { ...props, wrapper: e.target } );
+ wrapper.addEventListener( 'wc-blocks_render_blocks_frontend', () => {
+ renderBlockInsideWrapper( { ...props, wrapper } );
} );
} );
};