phpwordpresswordpress-gutenberggutenberg-blocks

InnerBlock not saving in self made custom plugin


I'm kinda new to WP development. I'm trying to build my own plugin that has custom block types for me to use. I've set it up that the js file of each custom blog has the html and styling for the editor. And the client side code has been placed in php files.

The problem i'm having right now is that i am trying to build my own hero block, where i've added an InnerBlocks tag. I allow the use of another custom block called GenericHeader. I can add the GenericHeader to the Hero block in the editor and when is save everything seems to work as expected. But when i refresh the GenericHeader block is gone. I'm not sure what i'm missing, but then again i'm not that used to WP development so i'm probably doing something completely wrong here.

Hero.js file

import "./hero-block.scss"
import {InnerBlocks} from "@wordpress/block-editor"

// custom block type hero
wp.blocks.registerBlockType("dotnyma-custom-blocks/hero", {
    title: "Hero",
    icon: "image",
    category: "common",
    attributes: {
        imageUrl: {type: "string"},
    },
    edit: EditHeroComponent,
    save: function () {
        return null;
    }
});


function EditHeroComponent(props) {
    const {attributes, setAttributes} = props;

    return (
        <>
            <div className="hero-editor-container">
                <img src={attributes.imageUrl} alt='test'/>
                <div className="hero-editor-content-container">
                    <InnerBlocks allowedBlocks={['dotnyma-custom-blocks/generic-header']} />
                </div>
            </div>
        </>
    )
}


hero.php file

<?php

class HeroBlock
{
    function heroBlockRender($attributes, $content, $block_class): string
    {
        $item_count = count($block_class->inner_blocks);

        if ( $item_count < 1 ) {
            return '<h1>No blocks found</h1>';
        }

        wp_enqueue_style('customBlockTypesStyles');
        ob_start();
        ?>
        <div class="hero-editor-container">
            <img src="<?php echo esc_html($attributes['imageUrl'])?>" alt='test'/>
            <div class="hero-editor-content-container">
               <?php
                   // iterate over the available inner blocks
               for ($index = 1; $index <= $item_count; $index++) :

                   // Get the inner block data
                   $inner_block = $block_class->inner_blocks->current();

                   // Holds the inner block attribute
                   $attribute = $inner_block->attributes;

                   // This will display the attributes data
                   var_dump($attribute);

                   // increase the index in the WP_Block_List class used to retrieve the current block
                   $block_class->inner_blocks->next();
               endfor;
               // reset the index in the WP_Block_List class to the initial state
               $block_class->inner_blocks->rewind();
               ?>
            </div>
        </div>
        <?php return ob_get_clean();
    }
}

?>

genericHeader.js

import "./generic-header.scss"
import { RichText }  from "@wordpress/block-editor";

// custom block type hero
wp.blocks.registerBlockType("dotnyma-custom-blocks/generic-header", {
    title: "Generic header",
    icon: "image",
    category: "common",
    attributes: {
        text: { type : "string"},
        size: { type : "string", default: "large" }
    },
    edit: EditGenericHeaderComponent,
    save: function () {
        return null;
    }
});


function EditGenericHeaderComponent(props) {
    const {attributes, setAttributes} = props;

    function handleTextChange(event) {
        console.log(event);
        setAttributes( {text: event} );
    }

    return (
        <>
            <RichText tagName={"h1"} value={attributes.text} onChange={handleTextChange}/>
        </>
    )
}

genericHeader.php file

<?php
class GenericHeaderBlock
{
    function genericHeaderRender(): string
    {
        wp_enqueue_style('customBlockTypesStyles');
        ob_start();
        ?>
        <h1>this is working</h1>
        <?php return ob_get_clean();
    }
}
?>

Index.js

import "./index.scss"
import "./blocks/hero/hero-block"
import "./blocks/generic-header/generic-header"

index.php

<?php

/*
    Plugin Name: ****************
    Description: **************
    Version: 1.0
    Author: ***********
    Author URI: ***********
*/

if (!defined('ABSPATH')) exit;

include (__DIR__ . "/src/blocks/hero/hero-block.php");
include (__DIR__ . "/src/blocks/generic-header/generic-header.php");

class DotNymaCustomBlocks
{
    function __construct()
    {
        add_action('init', array($this, 'customBlockAssets'));
    }

    function customBlockAssets(): void
    {
        $heroBlock = new HeroBlock();
        $genericHeaderBlock = new GenericHeaderBlock();

        wp_register_style('customBlockTypesStyles', plugin_dir_url(__FILE__) . 'build/index.css');
        wp_register_script('customBlockTypes', plugin_dir_url(__FILE__) . 'build/index.js', array('wp-blocks', 'wp-element', 'wp-editor'));

        register_block_type('dotnyma-custom-blocks/hero', array(
            'editor_script' => 'customBlockTypes',
            'editor_style' => 'customBlockTypesStyles',
            'render_callback'  => [ $heroBlock, 'heroBlockRender' ]
        ));
        register_block_type('dotnyma-custom-blocks/generic-header', array(
            'editor_script' => 'customBlockTypes',
            'editor_style' => 'customBlockTypesStyles',
            'render_callback' => [$genericHeaderBlock, 'genericHeaderRender']
        ));
    }
}

$dotNymaCustomBlocks = new DotNymaCustomBlocks();
?>


Solution

  • Innerblocks need to be saved in the save component. The save component is responsible for rendering the HTML that will be saved to as the content of the post or page which can then be rendered onto the frontend.

    Having a render_callback is not necessary to make this work. However, the render_callback can be very useful to add any additional logic that requires server side rendering, like getting the latests posts. In this case the saved HTML will be accessible in the $content argument of the render_callback.

    import "./hero-block.scss"
    import { useBlockProps, useInnerBlocksProps } from "@wordpress/block-editor"
    import { registerBlockType } from "@wordpress/blocks";
    
    registerBlockType("dotnyma-custom-blocks/hero", {
      title: "Hero",
      icon: "image",
      category: "common",
      attributes: {
        imageUrl: { type: "string" },
      },
      allowedBlocks: ["dotnyma-custom-blocks/generic-header"],
      edit: EditHeroComponent,
      save: SaveHeroComponent
    });
    
    
    function EditHeroComponent(props) {
      const { attributes, setAttributes } = props;
      const blockProps = useBlockProps({
        className: 'hero-editor-container'
      });
    
      const innerBlocksProps = useInnerBlocksProps({
        className: 'hero-editor-content-container'
      });
    
      return (
        <div {...blockProps}>
          <img src={attributes.imageUrl} alt='test' />
          
          <div {...innerBlocksProps} />
        </div>
      )
    }
    
    function SaveHeroComponent(props) {
      const { attributes } = props;
      const blockProps = useBlockProps.save({
        className: 'hero-editor-container'
      });
    
      const innerBlocksProps = useInnerBlocksProps.save({
        className: 'hero-editor-content-container'
      });
    
      return (
        <div {...blockProps}>
          <img src={attributes.imageUrl} alt='test' />
          
          <div {...innerBlocksProps} />
        </div>
      )
    }