I've got a block that allows the user to enter a script when in edit mode, then sends this script to a REST endpoint which returns a WP_Post
representing a media file which is then displayed to the user. The REST endpoint can take many seconds (even minutes) to send a reply, so I want to do this updating asynchronously and display a spinner while the user waits.
The problem is that the spinner shows indefinitely, even after the REST endpoint returns its value. I'm not sure what I'm doing wrong. Here is my (abbreviated) block code:
( function( wp ) {
var registerBlockType = wp.blocks.registerBlockType;
var withSelect = wp.data.withSelect;
var el = wp.element.createElement;
var Component = wp.element.Component;
var Spinner = wp.components.Spinner;
var __ = wp.i18n.__;
function getEditComponent( blockName, blockTitle ) {
return class extends Component {
render() {
const { mediaFile, isSelected, className, attributes, setAttributes } = this.props;
const { script } = attributes;
// Check if the mediaFile has been populated via REST yet.
const hasMedia = Array.isArray( mediaFile ) && mediaFile.length;
if ( isSelected ) {
// Show edit textarea.
return el(
'div',
{ className },
el(
'label',
null,
__( 'Script:', 'my-textdomain' ),
el(
'textarea',
{
onChange: ( event ) => {
// Update script attribute as it is typed.
setAttributes( { script: event.target.value } );
},
value: script,
style: {
width: '100%'
},
spellCheck: false,
placeholder: __( 'Enter script', 'my-textdomain' ),
}
)
)
);
} else {
if ( ! hasMedia ) {
return (
el(
Placeholder,
{
label: __( 'label', 'my-textdomain' ),
},
// Display spinner until mediaFile has been returned from REST.
! Array.isArray( mediaFile ) ? Spinner() : __( 'Media is being generated.', 'my-textdomain' ),
)
);
}
return el(
'img',
{
src: mediaFile.guid || '', // Display mediaFile's URL.
}
);
}
}
};
};
const name = 'my-namespace/my-block';
const title = __( 'Script', 'my-textdomain' );
const edit = getEditComponent( name, title );
registerBlockType( name, {
// Various properties here, like title, description, etc...
edit: withSelect( ( select, props ) => {
const { attributes } = props;
const { script } = attributes;
// Get media file using script.
const mediaFile = wp.apiFetch(
{
path: '/my-api-namespace/v1/do-thing',
method: 'POST',
data: {
script: script,
},
}
);
return {
mediaFile,
};
} )( edit ),
save: null, // not shown here
} );
} )(
window.wp
);
The edit function is where the API fetch is made, and it is written into the edit class's property mediaFile
. Initially this is of type Promise
returned by wp.apiFetch
, but eventually (I think) it becomes the result of that fetch (in my case a WP_Post
array).
The edit class's render
function checks whether mediaFile
is populated or not, and decides whether to show a "loading" spinner or the actual media file image.