I am trying to add some html from one of my functions the edit and save funktion. If I do it manually it works:

return <p>Hello World</p>;

But if I do it with a function that returns a dom element, I get this error:

Gutenberg Block: Objects are not valid as a React child (found: [object HTMLDivElement]).

But if I do a console.info chrome displays the html. Any idea why this does not work?

registerBlockType( 'simpletoc/toc', {
    title: __( 'SimpleTOC', 'simpletoc' ),
    icon: listul,
    category: 'layout',
    edit: function( props ) {
            const data = wp.data.select( 'core/block-editor' );
            const blocks = data.getBlocks();
      return generateTOC(blocks);
    save: ( props ) => {
          return generateTOC(blocks);
} );

function generateTOC(blocks){
    var div = document.createElement('div');
    var ul = document.createElement('ul');
    var h2 = document.createElement('h2');
    var headline = __( 'Table of Contents', 'simpletoc' );

    blocks.forEach(function (item,index){
        var blockId = '';
        var slug = '';

        h2 = document.createTextNode(headline)
        var title = '';
        if ( item['name'] === 'core/heading' ) {
            blockId = (item['clientId']);
            title = item.attributes.content;
            slug = item.attributes.content.toSlug();
            var li = document.createElement('li');


    return div;
  • Are you sure it's possible to return raw DOM nodes? WP provides a wrapper for components that should handle all of this, and it'll convert actual HTML tags into components if you use WP Scripts as well. What does setHeadingAnchors do?
    – Tom J Nowell
    Apr 15, 2020 at 21:05
  • I removed setHeadingAnchors(). Has nothing to do with this problem. It sets anchors to all headings. Ok what is the wrapper then? How do I set HTML programmatically?
    – Marc
    Apr 15, 2020 at 21:21
  • I'd recommend taking a look at this example: github.com/WordPress/gutenberg-examples/tree/master/… Mainly as it shows the preferred way to build the JS into the final product, and what a basic block is supposed to look like
    – Tom J Nowell
    Apr 15, 2020 at 21:22
  • Here's the non-esnext JS version github.com/WordPress/gutenberg-examples/tree/master/01-basic
    – Tom J Nowell
    Apr 15, 2020 at 21:22

1 Answer 1


Instead of using raw DOM nodes, use the WP Element wrapper components and build it with wp scripts

For example here is an ESNext block using modern JS:

    edit() {
        return (
            <div style={ blockStyle }>
                Hello World, step 1 (from the editor).

And again using raw JS without the build step:

( function( blocks, i18n, element ) {
    var el = element.createElement;
        edit: function() {
            return el(
                { style: blockStyle },
                'Hello World, step 1 (from the editor).'
} )( window.wp.blocks, window.wp.i18n, window.wp.element );

Here's what your edit function might look like using modern JS:

    edit: function( props ) {
        const data = wp.data.select( 'core/block-editor' );
        const blocks = data.getBlocks();
        const headings = blocks.map( ( item, index ) => {
            if ( item.name === 'core/heading' ) {
                return null;
            return <li>{item.attributes.content}</li>;
        } );
        return <div>
            <h2>{ __( 'Table of Contents', 'simpletoc' ) }</h2>

Then we can use wp scripts to build it:


As a result it will build the script and handle all the JSX and make sure it uses the WordPress element wrapper ( otherwise you'd have to use el( 'p' etc. It'll even give you a PHP file that returns an array of JS dependencies for wp_enqueue_script so that your built JS stays super lean, and gets loaded at the correct time in the editor


  • item['name'] will give you issues, item is an object, not an array
  • indent! Indent correctly and consistently
  • It looks like you have a funciton setHeadingAnchors that meddles in the affairs of other blocks, I'd advise against this
  • You should use this code in your save function as well, the only thing your PHP needs to do is enqueue the JS, and ensure any assets needed on the frontend are loaded
  • Thank you! a) You mean item.name instead of item['name']? b) Where can I look indents up or is there an atom plugin that does that for me? I use beautfiy. c) But I need to set the anchors of other blocks for the toc to work.. any other idea? d) But I did, didn't i? I used generateTOC in "edit" and "save" Here is the complete code btw: github.com/mtoensing/simpletoc/tree/react-only
    – Marc
    Apr 15, 2020 at 21:37
  • Your editor should indent automatically, and have auto-indenting functionality. You might need to install a package for that last one though. I don't use Atom myself. As for anchors for other blocks, that would make a good question, though to be able to link in a TOC you only need the headings to have IDs
    – Tom J Nowell
    Apr 15, 2020 at 21:42
  • Thank you so much. I learned a few things here. May I ask why it is best practise to use constants in your example? Why constants headings? Why not just "var"?
    – Marc
    Apr 16, 2020 at 5:23
  • And now I understand that I can just write html and fill it with code. Great! There really should be a tutorial that helps people who are not that familiar with this new kind of javascript to give them a head start.
    – Marc
    Apr 16, 2020 at 5:24
  • Look up any React tutorial, the syntax is the same. As for const, if a variable isn't meant to change, use const, that way if it accidentally gets changed it'll cause an error and you'll know exactly where the bug is. Look into immutable objects and JS for more of the reasoning behind it. Also instead of var, most would now use let. ESLint will help check for these things, but a lot of the newer language features are under the umbrella of ESNext, with year names at the end for which year introduced them. Things like arrow functions, spread operators, real classes, imports/exports, etc
    – Tom J Nowell
    Apr 16, 2020 at 11:11

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.