Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hydrate Cart Order Summary Coupon Form inner block using BHE #54

Merged
merged 14 commits into from
Aug 26, 2022
Merged
14 changes: 12 additions & 2 deletions assets/js/atomic/utils/render-parent-block.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,18 @@ const renderInnerBlocks = ( {
return null;
}
return Array.from( children ).map( ( node: Node, index: number ) => {
/**
* Do not process the node if its a `<wp-block>` element.
*/
if (
node instanceof HTMLElement &&
node instanceof window.customElements.get( 'wp-block' )
) {
const reactElement = parse( node.outerHTML );

if ( isValidElement( reactElement ) )
return cloneElement( reactElement );
}
/**
* This will grab the blockName from the data- attributes stored in block markup. Without a blockName, we cannot
* convert the HTMLElement to a React component.
Expand All @@ -151,7 +163,6 @@ const renderInnerBlocks = ( {
...( node instanceof HTMLElement ? node.dataset : {} ),
className: node instanceof Element ? node?.className : '',
};

const InnerBlockComponent = getBlockComponentFromMap(
blockName,
blockMap
Expand All @@ -169,7 +180,6 @@ const renderInnerBlocks = ( {
node?.textContent ||
''
);

// Returns text nodes without manipulation.
if ( typeof parsedElement === 'string' && !! parsedElement ) {
return parsedElement;
Expand Down
31 changes: 31 additions & 0 deletions assets/js/base/utils/bhe-blocks.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* External dependencies
*/
import { ReactElement } from 'react';
import { useBlockProps, useInnerBlocksProps } from '@wordpress/block-editor';
import { registerBlockType as gutenbergRegisterBlockType } from '@wordpress/blocks';

const Wrapper =
( Comp: ReactElement ) =>
( { attributes } ) =>
(
<>
{ /* Block Context is not available during save
https://wordpress.slack.com/archives/C02QB2JS7/p1649347999484329 */ }
<Comp
blockProps={ useBlockProps.save() }
attributes={ attributes }
context={ {} }
>
<wp-inner-blocks { ...useInnerBlocksProps.save() } />
</Comp>
</>
);

export const registerBlockType = ( name, { edit, view, ...rest } ) => {
gutenbergRegisterBlockType( name, {
edit,
save: Wrapper( view ),
...rest,
} );
};
66 changes: 54 additions & 12 deletions assets/js/base/utils/bhe-element.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,59 @@
/* eslint-disable react-hooks/rules-of-hooks */

/**
* External dependencies
*/
import {
createContext,
useContext as useReactContext,
useEffect as useReactEffect,
useState as useReactState,
} from '@wordpress/element';
import { hydrate as ReactHydrate } from 'react-dom';
import { ReactElement } from 'react';

type HydrateOptions = {
technique?: 'media' | 'view' | 'idle';
media?: string;
export const EnvContext = createContext( null );

/**
* A React hook that returns the name of the environment.
*
* This is still a bit hacky. Ideally, Save components should support React
* hooks and all the environments (Edit, Save and Frontend) should populate a
* normal context. Also, more environments could be added in the future.
*
* @return A string with the environment the component is loaded, can be {"edit" | "save" | "frontend"}
*/
export const useBlockEnvironment = () => {
try {
const env = useReactContext( EnvContext );
if ( env === 'frontend' ) {
return 'frontend';
}
return 'edit';
} catch ( e ) {
return 'save';
}
};

export const hydrate = (
element: ReactElement,
container: Element,
hydrationOptions: HydrateOptions = {}
) => {
const { technique, media } = hydrationOptions;
const noop = () => null;

export const useState = ( init ) =>
useBlockEnvironment() !== 'save' ? useReactState( init ) : [ init, noop ];

export const useEffect = ( ...args ) =>
useBlockEnvironment() !== 'save' ? useReactEffect( ...args ) : noop;

export const useContext = ( Context ) =>
useBlockEnvironment() !== 'save'
? useReactContext( Context )
: Context._currentValue;

export const hydrate = ( element, container, hydrationOptions ) => {
const { technique, media } = hydrationOptions || {};

const cb = () => {
ReactHydrate( element, container );
};

switch ( technique ) {
case 'media':
if ( media ) {
Expand All @@ -29,8 +65,10 @@ export const hydrate = (
}
}
break;

// Hydrate the element when is visible in the viewport.
// https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API

case 'view':
try {
const io = new IntersectionObserver( ( entries ) => {
Expand All @@ -41,23 +79,27 @@ export const hydrate = (
// As soon as we hydrate, disconnect this IntersectionObserver.
io.disconnect();
cb();
break; // break loop on first match
break; // Break loop on first match.
}
} );
io.observe( container.children[ 0 ] );
} catch ( e ) {
cb();
}
break;

case 'idle':
// Safari does not support requestIdleCalback, we use a timeout instead. https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback
// Safari does not support requestIdleCalback, we use a timeout instead.
// https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback
if ( 'requestIdleCallback' in window ) {
window.requestIdleCallback( cb );
} else {
setTimeout( cb, 200 );
}
break;

// Hydrate this component immediately.

default:
cb();
}
Expand Down
Loading