admin管理员组文章数量:1122846
I have this specific block
than when I insert in the Editor, gives me this error:
and the error states:
Content generated by
save
function:
<div class="data-props" style="display:none;"><!--{"styleMode":"light","ctaTarget":"false","isImage":"true","isCardLeft":"true","mediaSrc":"","previewImage":"","className":"my-block light-mode"}--></div><div class="my-block light-mode" data-props="prev-sibling"><div class="my-block my-block light-mode"><div class="my-block"><div class="cnt-light cnt-maxwidth card-on-left"><div class="my-block__card"><div data-innerblocks="1"></div></div></div></div></div></div>
Content retrieved from post body:
<div class="my-block light-mode"><div class="data-props" style="display:none;"><!--{"ctaTarget":"false","isImage":"true","styleMode":"light","isCardLeft":"true","mediaSrc":"","previewImage":""}--></div><div class="my-block" data-props="prev-sibling"><div class="my-block"><img class="background__image" id="image-bg-desktop" src="/wp-content/themes/myTheme/assets/media/image.png" alt="my-block"/><div class="cnt-light cnt-maxwidth card-on-left"><img class="background__image" id="image-bg-mobile" src="/wp-content/themes/myTheme/assets/media/image.png" alt="my-block"/><div class="my-block__card"><div data-innerblocks="1"></div></div></div></div></div></div>
The React Component's code:
import { HydratedInnerBlock } from '../../../src/components/HydratedInnerBlock';
import { DEFAULT_IMAGE } from '../../../src/constants';
import { useStyleModeClassName } from '../../../src/hooks/useStyleModeClassName';
import { cssClassName } from '../../../src/util/cssClassName';
export const MyComponent = ( props ) => {
const { mediaSrc, isImage, styleMode, isCardLeft, innerBlockProps } = props;
const classNames = useStyleModeClassName( styleMode );
const [ baseClassName ] = classNames;
return (
<div className="my-block">
{ isImage && (
<img
className="background__image"
id="image-bg-desktop"
src={ mediaSrc || DEFAULT_IMAGE }
alt="my-block"
/>
) }
{ ! isImage && (
<video
autoPlay
loop
muted
src={ mediaSrc }
className="background__video"
/>
) }
<div
className={ `cnt-${ baseClassName } ${ cssClassName(
'cnt-maxwidth ',
{
'card-on-left': isCardLeft,
'card-on-right': ! isCardLeft,
}
) }` }
>
{ isImage && (
<img
className="background__image"
id="image-bg-mobile"
src={
mediaSrc || DEFAULT_IMAGE
}
alt="my-block"
/>
) }
{ ! isImage && (
<video
autoPlay
loop
muted
src={ mediaSrc }
className="background__video"
/>
) }
<div className="my-block__card">
<HydratedInnerBlock innerBlockProps={ innerBlockProps } />
</div>
</div>
</div>
);
};
Here is the HydratedInnerBlock
component:
export function HydratedInnerBlock( {
innerBlockProps,
onRef,
...otherDirectProps
} ) {
return (
<div
data-innerblocks="1"
dangerouslySetInnerHTML={ {
__html:
innerBlockProps?.children?.props?.children ||
innerBlockProps,
} }
ref={ onRef }
{ ...otherDirectProps }
/>
);
}
The block's Editor's code:
import { useEffect } from '@wordpress/element';
import {
useBlockProps,
InspectorControls,
MediaUpload,
useInnerBlocksProps,
} from '@wordpress/block-editor';
import { Panel, PanelBody, ToggleControl } from '@wordpress/components';
import { DEFAULT_IMAGE } from '../../src/constants';
import { EditorFileSelector } from '../../src/components/EditorFileSelector';
import './Editor.scss';
import { cssClassName } from '../../src/util/cssClassName';
import { useStyleModeClassName } from '../../src/hooks/useStyleModeClassName';
import { ColorModeSelector } from '../../src/components/ColorModeSelector';
/**
* The edit function describes the structure of your block in the context of the
* editor. This represents what the editor will render when the block is used.
* Note this is a client-side component, so you can use client-side functionality
* (hooks, events, ajax calls, etc)
*/
const TEMPLATE = [
[
'core/heading',
{
content:
'Content 1',
level: 2,
className: 'my-block__title',
},
],
[
'core/heading',
{
content: 'Content2',
level: 5,
className: 'my-block__subtitle',
},
],
[
'core/paragraph',
{
content:
'Content3',
className: 'my-block__description',
},
],
[
'core/buttons',
{},
[
[
'core/button',
{
text: 'Get Started',
className: 'btn btn-dark',
},
],
],
],
];
export const Editor = ( {
attributes: {
styleMode,
mediaSrc,
ctaTarget,
isImage,
isCardLeft,
previewImage,
},
setAttributes,
} ) => {
const blockProps = useBlockProps();
const classNames = useStyleModeClassName( styleMode );
const [ baseClassName, invertedClassName ] = classNames;
const innerBlockProps = useInnerBlocksProps( undefined, {
template: TEMPLATE,
} );
useEffect( () => {
const button = document.querySelector( '.btn' );
if ( button ) {
button.className = '';
button.classList.add( 'btn', `btn-${ invertedClassName }` );
}
}, [ invertedClassName ] );
const handleMediaUpload = ( value ) => {
setAttributes( { mediaSrc: value.url } );
};
const handleLinkTab = ( value ) => {
setAttributes( { ctaTarget: value } );
};
const handleMediaTypeChange = ( value ) => {
setAttributes( { isImage: value } );
};
const handleCardPosition = ( value ) => {
setAttributes( { isCardLeft: value } );
};
const handleSelectStyleMode = ( value ) => {
setAttributes( { styleMode: value } );
};
if ( previewImage?.length > 0 ) {
return (
<img
src={ previewImage }
alt="Preview"
style={ { width: '470px' } }
/>
);
}
return (
<div
{ ...blockProps }
className="my-block"
>
<div className="my-block">
{ isImage && (
<img
className="background__image"
id="image-bg-desktop"
src={
mediaSrc || DEFAULT_IMAGE
}
alt="my-block"
/>
) }
{ ! isImage && (
<video
autoPlay
loop
muted
src={ mediaSrc }
className="background__video"
/>
) }
<div
className={ `cnt-${ baseClassName } ${ cssClassName(
'cnt-maxwidth ',
{
'card-on-left': isCardLeft,
'card-on-right': ! isCardLeft,
}
) }` }
>
{ isImage && (
<img
className="background__image"
id="image-bg-mobile"
src={
mediaSrc ||
DEFAULT_IMAGE
}
alt="my-block"
/>
) }
{ ! isImage && (
<video
autoPlay
loop
muted
src={ mediaSrc }
className="background__video"
/>
) }
<div className="my-block__card">
<div { ...innerBlockProps } />
</div>
</div>
<InspectorControls>
<Panel>
<PanelBody
title="Component Settings"
icon="admin-plugins"
>
<ColorModeSelector
value={ styleMode }
onChange={ handleSelectStyleMode }
useDefaultColors={ true }
/>
<ToggleControl
label="Position card to the left"
checked={ isCardLeft }
onChange={ handleCardPosition }
/>
<ToggleControl
label="Open Cta Link in a new tab"
checked={ ctaTarget }
onChange={ handleLinkTab }
/>
<ToggleControl
label="Background is an image"
checked={ isImage }
onChange={ handleMediaTypeChange }
/>
<MediaUpload
onSelect={ handleMediaUpload }
render={ ( { open } ) => (
<EditorFileSelector
open={ open }
label="Upload Media"
buttonLabel="Select / Upload Media"
/>
) }
allowedTypes={ [ 'image', 'video' ] }
/>
</PanelBody>
</Panel>
</InspectorControls>
</div>
</div>
);
};
And its Save.js:
/**
* The 'Save' component of the block. This renders on server-side only
* We separate this initializer from the 'Form' component because
* the 'Form' component would also be used after hydration.
*
*/
export const Save = ( { attributes } ) => {
const blockProps = useBlockProps.save();
const innerBlockProps = useInnerBlocksProps.save();
const { isDarkMode } = attributes;
const blockState = JSON.stringify( {
...attributes,
} )
// remove html comments
.replace( /<\!--.+?-->/g, '' );
return (
<>
<div
className="data-props"
style="display:none;"
dangerouslySetInnerHTML={ {
__html: `<!--${ blockState }-->`,
} }
/>
<div
className={ cssClassName(
`my-block `,
{
'dark-mode': isDarkMode,
'light-mode': ! isDarkMode,
}
) }
data-props="prev-sibling"
>
<div { ...blockProps }>
<MyComponent
{ ...attributes }
innerBlockProps={ innerBlockProps }
/>
</div>
</div>
</>
);
};
The Save.js has that structure because I rehydrate the block after selecting custom attributes (theme dark/light) from custom metaboxes from the Editor's panel:
/**
* Initializes the client-side component.
* Hydrates the server-side rendered component and initializes client-side functionality.
*
*/
const MyBlock = (props) => {
const { styleMode } = props;
const classNames = useStyleModeClassName(styleMode);
const [baseClassName, invertedClassName] = classNames;
useEffect(() => {
const button = document.querySelector(".btn");
if (button) {
button.className = "";
button.classList.add("btn", `btn-${invertedClassName}`);
}
}, [baseClassName, invertedClassName]);
return <MyComponent {...props} />;
};
/**
* If we have any blocks hydrate the server-side renderered HTML
*/
hydrate(
".my-block",
MyBlock,
true
);
Here is my hydrate
function, if it helps:
import { createRoot } from '@wordpress/element';
/**
* Helper function to hydrate any custom block following our approach for hydration
*
* @param {string} selector indicates the identifier for the container of the block to hydrate
* @param {Function} Component indicates the react component to hydrate with
* @param {boolean} isUseInnerBlocks indicates the react component uses inner blocks, to hydrate them recursively
*/
export function hydrate( selector, Component, isUseInnerBlocks ) {
// mutation observer
const mutationObserverConfig = {
attributes: true,
childList: true,
subtree: isUseInnerBlocks ? true : false,
};
const initializeBlocks = () => {
const elements = document.querySelectorAll( selector );
if ( elements.length ) {
elements.forEach( ( element ) => {
if ( element.attributes[ 'data-hydrated' ]?.value === '1' ) {
// already hydrated, ignore
return;
}
// read hydration data
const closestParent = element.closest( '[data-props]' );
if ( ! closestParent ) {
// no props, nothing to hydrate
return;
}
let attributeValue =
closestParent.attributes[ 'data-props' ].value;
if ( attributeValue === 'prev-sibling' ) {
const indexOf = Array.prototype.indexOf.call(
closestParent.parentNode.childNodes,
closestParent
);
attributeValue =
closestParent.parentNode.childNodes[ indexOf - 1 ]
.childNodes[ 0 ].textContent;
}
const data = JSON.parse( attributeValue );
const innerBlockPropsStr = isUseInnerBlocks
? closestParent
.querySelector( '[data-innerblocks]' )
?.innerHTML?.replace( /data-hydrated="1"/gi, '' )
: undefined;
// render new instance replacing the existing element (hydrate)
const root = createRoot( element );
root.render(
<Component
{ ...data }
innerBlockProps={ innerBlockPropsStr }
/>
);
element.setAttribute( 'data-hydrated', '1' );
} );
}
};
// eslint-disable-next-line no-undef
const observer = new MutationObserver( initializeBlocks );
// Start observing the target node for configured mutations
observer.observe( document.body, mutationObserverConfig );
}
I changed the structure of the Save.js, fixing the WordPress error, but that breaks the hydrate
function, and results in keeping the default theme (dark) and doesn't update its colors when changing to light mode.
Thanks in advance for your help!
本文标签: errorsBlock validation failed for React Component block that rehydrates after taking effect
版权声明:本文标题:errors - Block validation failed for React Component block that rehydrates after taking effect 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1736295873a1929665.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论