Is it possible to use <ServerSideRender /> to create a “switchable” preview of a Carousel Gutenberg block?

wordpress.stackexchange https://wordpress.stackexchange.com/questions/386194

  •  26-04-2021
  •  | 
  •  

Question

Let's have a block that creates a Carousel using <InnerBlocks />:

edit.js of the parent component returns something like:


    return(
            <div className="carousel">
                <div className="carousel__track">
                        <ul {...innerBlocksProps} className="carousel__list" />
                </div>
            </div>
          )

edit.js of the child returns something like:

   return(
        <li class="slide">
            <figure>
                <img src={url} data-id={id} alt={title} />
                <figcaption>
                   <h5>{caption}</h5>
                </figcaption>
            </figure>
        </li>
      )

In the parent edit.js I would create a state like; const [isPreview, setIsPreview] = useState(false) and when I click a "edit" button the state is set to false, when I click a "preview" button the state is set to true.

I thought that when the parent state isPreview is true I could render the Carousel via <ServerSideRender />, so I've created a server-side rendered block, let's call it "myblocks/carousel-preview" and I've changed the edit.js parent like that:

 return(

         {
          !isPreview
          ?
            <div className="carousel">
                <div className="carousel__track">
                        <ul {...innerBlocksProps} className="carousel__list" />
                </div>
            </div>
          )
          :
          <>
            <ServerSideRender
            block="myblocks/carousel-preview"
            attributes={{ /* pass children here?!? */ }}
        />
          </> 

My main problem is: how can I pass to my <ServerSideRender /> block the children of my block?

In fact, I thought that the markup of the <ServerSideRender /> could be something like:

    
    <?php 
     function RenderCarouselServerSide($attributes, $content) {
       ob_start();
        ?>
      <div class="carousel">
        <div class="carousel__track">
          <ul class="carousel__list" />
           <?php foreach($attributes['children'] as $child) :
           $url = $child['url'];
           $id = $child['id'];
           $caption = $child['caption'];
            ?>
           <li class="slide">
            <figure>
                <img src="<?php echo $url; ?>" data-id="<?php echo $id; ?>" />
                <figcaption>
                   <h5>"<?php echo $caption; ?>"</h5>
                </figcaption>
            </figure>
          </li>
         </ul>
          <?php endforeach; ?>

          </div>
      </div>

     <?php
        return ob_get_clean();
    }

But how to pass the children as $attributes?

Was it helpful?

Solution

My main problem is: how can I pass to my <ServerSideRender /> block the children of my block?

Well actually, you might not need to use ServerSideRender..

If you just wanted to see a preview of the block output coming from the save() function, then you could simply use getSaveElement() to get that output, or the element returned by the save function.

Here's an example where I'm using the useBlockProps hook introduced in block API version 2: (these are all for the main/parent block)

  • Variables I used:

    // Used in the 'edit' and 'save' functions.
    const { useBlockProps, InnerBlocks } = wp.blockEditor;
    
    // Used in the 'edit' function.
    const { useState } = wp.element;
    const { Button } = wp.components;
    
    // Used in the preview function.
    const { select } = wp.data;
    const { getSaveElement } = wp.blocks;
    
  • My edit function:

    const edit = ( { clientId } ) => {
        const blockProps = useBlockProps();
    
        const [ isPreview, setIsPreview ] = useState( false );
        const setPreviewMode = () => setIsPreview( ! isPreview );
    
        const innerBlocksProps = { /* your code */ };
    
        return (
            <div { ...blockProps }>
                { isPreview ? (
                    <PreviewBlock
                        clientId={ clientId }
                    />
                ) : (
                    <div className="carousel">
                        <div className="carousel__track">
                            <ul { ...innerBlocksProps } className="carousel__list">
                                <InnerBlocks allowedBlocks={ [ 'myblocks/carousel-child' ] } />
                            </ul>
                        </div>
                    </div>
                ) }
                <Button onClick={ setPreviewMode }>{ isPreview ? 'Close Preview' : 'Preview' }</Button>
            </div>
        );
    };
    
  • My save function:

    const save = () => {
        const blockProps = useBlockProps.save( { className: 'carousel' } );
    
        const innerBlocksProps = { /* your code */ };
    
        return (
            <div { ...blockProps }>
                <div className="carousel__track">
                    <ul { ...innerBlocksProps } className="carousel__list">
                        <InnerBlocks.Content />
                    </ul>
                </div>
            </div>
        );
    };
    
  • And my preview function/component:

    function PreviewBlock( { clientId } ) {
        const { getBlock } = select( 'core/block-editor' );
    
        const block = getBlock( clientId );
    
        return getSaveElement( 'myblocks/carousel', block.attributes, block.innerBlocks );
    }
    

If you would rather use ServerSideRender, though:

Or if you just need to, then you can use getBlocks() to retrieve the inner/child blocks, then get the attributes of each of the child block, and include the attributes in the attributes property of the ServerSideRender element.

So based on my code above, I would only need to make these two changes:

  • In the Variables I used part, replace the const { getSaveElement } = wp.blocks; with const ServerSideRender = wp.serverSideRender;.

  • Then change the preview function to:

    function PreviewBlock( { clientId } ) {
        const { getBlocks } = select( 'core/block-editor' );
    
        // Get the attributes of the inner/child blocks.
        const blocks = getBlocks( clientId ).map( block => block.attributes );
    
        return (
            <ServerSideRender
                block="myblocks/carousel-preview"
                attributes={ { children: blocks } }
                httpMethod="POST"
            />
        );
    }
    

    Note that the httpMethod (HTTP request method) doesn't have to be set to POST, but POST will allow a bigger attributes object.

And remember, you must register the above children attribute in PHP (via register_block_type()). E.g.

Note: The attribute name doesn't have to be children. You can use any other name you like. But be sure the name matches the one used in the preview function above.

register_block_type( 'myblocks/carousel-preview', array(
    'apiVersion'      => 2,
    'render_callback' => 'RenderCarouselServerSide',
    'attributes'      => array(
        'children' => array(
            'type'    => 'array',
            'default' => array(),
        ),
    ),
) );

So I hope that helps and you might want to check my answer here which sends the block content instead of the inner blocks atrributes, to the server-side renderer.

Licensed under: CC-BY-SA with attribution
Not affiliated with wordpress.stackexchange
scroll top