Wordpress registerBlockType, data not fetched when saved

Viewed 441

I have a simple block, code below, that contains a component that fetches image data, including a URL and a pid, using a custom REST call. The registerBlockType edit method contains the component, displaying the image, and a number control. With fetch calls in the component's componentDidUpdate() & componentDidMount() methods, when the value of the number control changes, new data is fetched and the image display updates accordingly. This works fine; the image updates and when I save using the update button in the page editor I can refresh the editor and the block loads with the updated image. However, when I try and preview the page, neither DidMount nor DidUpdate are called in the save method and the preview page shows my image failed to load message. Any idea how I deal with this?

index.js:


import { registerBlockType } from '@wordpress/blocks';
import { useBlockProps } from '@wordpress/block-editor';
import { __experimentalNumberControl as NumberControl } from '@wordpress/components';

import {PnImage} from './PnImage.js'

registerBlockType( 'create-block/pn-test', {
    
    attributes: {
        pid: {
            type: "number",
            default: 8
        },
    },

    edit: ( {attributes, setAttributes} ) => {
    
        console.log( "Edit", attributes )
        return (
            <p { ...useBlockProps() }>
                 {"Editoor " + attributes.pid}
                <PnImage pid={attributes.pid}/>
                <NumberControl
                    label="PID"
                    isShiftStepEnabled={ true }
                    onChange={ value => { setAttributes( {pid: value})} }
                    shiftStep={ 10 }
                    value={ attributes.pid }
                />

            </p>
        )
    },

    save: ({ attributes }) => {

        return (
            <p { ...useBlockProps.save() }>
                { "Saved " + attributes.pid}
                <PnImage pid={attributes.pid}/>c
            </p>
        )
    }
})

PnImage.js, component code

import React, { Component } from 'react';

export class PnImage extends Component {
    
    static defaultProps = {
        size:   "large",
        pid:    null,
    }

    constructor(props) {
        super(props);
        this.state = {
            image: null,
            pid: null
        };
    }

    componentDidMount() {           // initially fetch data
        console.log( "DidMount, pid: ", this.props.pid)
        this.Fetch()
    }
    componentDidUpdate() {          // fetch data when props.pid changes
        console.log( "DidUpdate, pid: ", this.props.pid)
        this.Fetch()
    }

    render() {

        const {image} = this.state;

        return (
            <p>
                Image Component<br/>
                { image ? <img src={image.URL} /> : "no  image found loaded for pid " + this.props.pid }
            </p>
        )
    }

    Fetch() { // fetch wrapper

        if (this.props.pid != this.state.pid && this.props.pid ) {
            console.log( "fetching: pid=", this.props.pid )
            pnRest.Fetch('image/get', { pid: this.props.pid, size: this.props.size })
                .then(data => {
                    if ( data ) {       // image not found returns undefined
                        this.setState({ image: data, pid: data.id })
                    } else {
                        console.log( "Couldn't find image for pid " + this.props.pid )
                    }
                });
        }

    }

}

2 Answers

I don't think you can run async functions inside the save() function. So it will never save the returned data. Instead what you should do is fetch the data, then save the fetched data to attributes of the block (e.g. pid and src. Then use those saved attributes in the save() function. You can pass the setAttributes() function into your PnImage class as a prop and access it directly:

<PnImage 
  pid={ attributes.pid }
  setAttributes={ setAttributes }
/>

Then, within Fetch() {}, something like this:

pnRest.Fetch('image/get', { 
    pid: this.props.pid, 
    size: this.props.size 
}).then(data => {
    if (data) {
        this.props.setAttributes({ 
            src: data.src, 
            pid: data.id 
        });
    }
);

After a bit of messing about, here's the solution I came up with. Working with Phil's suggestion, rather than pass setAttributes to the pnImage component, I've shifted the Fetch function into my edit function, using it in the number control callback and, so the component can be populated when it's first mounted, I pass Fetch to the component and call it in componentDidMount. Seems to work well enough.

Thanks for the help, Phil.


import { registerBlockType } from '@wordpress/blocks';
import { useBlockProps } from '@wordpress/block-editor';
import { __experimentalNumberControl as NumberControl } from '@wordpress/components';

import {PnImage} from './PnImage.js'

registerBlockType( 'create-block/pn-test', {
    
    attributes: {
        pid: {
            type: "number",
            default: 8
        },
        image:  {
            type: "object",
            default: null
        },
    },

    edit: ( {attributes, setAttributes} ) => {
    
        console.log( "Edit", attributes )

        function Fetch( pid ) { // fetch wrapper

            pnRest.Fetch('image/get', { pid: pid, size: 'large' })
                .then(data => {
                    if ( data ) {       // image not found returns undefined
                        setAttributes({ image: data, pid: pid })
                    } else {
                        console.log( "Couldn't find image for pid <" + pid + ">" )
                        setAttributes({ image: null,  pid: -1 })
                    }
                });
        }

        return (
            <p { ...useBlockProps() }>

                <PnImage 
                    pid={attributes.pid} 
                    image={attributes.image}
                    Fetch={Fetch}
                />

                <NumberControl
                    label="PID"
                    isShiftStepEnabled={ true }
                    onChange={ value => { if ( value == "" ) {return }; Fetch( value ) } }
                    shiftStep={ 10 }
                    value={ attributes.pid }
                />

            </p>
        )
    },

    save: ({ attributes, setAttributes }) => {

        return (
            <p { ...useBlockProps.save() }>
                <PnImage 
                    pid={attributes.pid} 
                    image={attributes.image}
                />
            </p>
        )
    }
})
import React, { Component } from 'react';

export class PnImage extends Component {
    
    static defaultProps = {
        size:   "large",
        pid:    null,
        image: null,
        Fetch: null
    }

    constructor(props) {
        super(props);

        this.state = {
            image: null,
            pid: null
        };
    }

    componentDidMount() {           // initially fetch data
        console.log( "DidMount, pid: ", this.props.pid)
        this.props.Fetch( this.props.pid )
    }
    render() {

        const image = this.props.image;

        if ( this.props.pid == -1 ) {
            return (
                <p>
                    Image couldn't be fetched
                </p>
            )
        }
        return (
            <p>
                Image Component<br/>
                { image ? <img src={image.URL} /> : "no  image found loaded for pid " + this.props.pid }
            </p>
        )
    }

}

Related