I'm attempting to store both an Image's URL and ID in a post's meta field via a Gutenberg component, but am having trouble figuring out how to store those multiple values in an array.
I can store a single value as a meta value without a problem. I can even store a single value in the first spot of an array without a problem. But I cannot seem to work out the proper syntax to take advantage of Wordpress's ability to store an array as a meta value.
The overarching goal is to override the image being used when a post is shared on social media. The issue is that I need to transfer both the URL and ID over to a PHP hook for it to work properly with a plugin (The SEO Framework). Unfortunately, I can't just store the ID because I need the URL to render the image within the Gutenberg component.
Full Component Code:
const OGImage = compose(
withDispatch( function( dispatch, props ) {
return {
setMetaValue: function( metaValue ) {
dispatch( 'core/editor' ).editPost(
{ meta: { [ props.metaKey ]: metaValue } }
);
}
}
} ),
withSelect( function( select, props ) {
return {
metaValue: select( 'core/editor' ).getEditedPostAttribute( 'meta' )[ props.metaKey ]
}
} ) )( function( props ) {
const fallbackInstructions = <p>{ __( 'To edit the OG image, you need permission to upload media.', 'atlantis') }</p>;
const onUpdateImage = img => { props.setMetaValue( img.url ); };
const onRemoveImage = () => { props.setMetaValue( null ); };
return (
<div className="og-image__wrapper">
<MediaUploadCheck fallback={ fallbackInstructions }>
<MediaUpload
title={ OG_IMAGE_LABEL }
onSelect={ onUpdateImage }
allowedTypes={ ALLOWED_MEDIA_TYPES }
render={ ( { open } ) => (
<Button
className={ ! props.metaValue ? 'og-image__toggle' : 'og-image__preview' }
onClick={ open }
aria-label={ ! props.metaValue ? null : EDIT_OR_UPDATE_OG_IMAGE_LABEL }>
{ !! props.metaValue &&
<img className='og-image__button' src={ props.metaValue } alt="" />
}
{ ! props.metaValue && SET_OG_IMAGE_LABEL }
</Button>
) }
/>
</MediaUploadCheck>
{ !! props.metaValue &&
<MediaUploadCheck>
<MediaUpload
title={ OG_IMAGE_LABEL }
onSelect={ onUpdateImage }
allowedTypes={ ALLOWED_MEDIA_TYPES }
render={ ( { open } ) => (
<Button onClick={ open } isDefault isLarge>
{ REPLACE_OG_IMAGE_LABEL }
</Button>
) }
/>
</MediaUploadCheck>
}
{ !! props.metaValue &&
<MediaUploadCheck>
<Button onClick={ onRemoveImage } isLink isDestructive>
{ REMOVE_OG_IMAGE_LABEL }
</Button>
</MediaUploadCheck>
}
</div>
);
}
);
export default OGImage;
Bits that matter:
const OGImage = compose(
withDispatch( function( dispatch, props ) {
return {
setMetaValue: function( metaValue ) {
dispatch( 'core/editor' ).editPost(
{ meta: { [ props.metaKey ]: metaValue } }
);
}
}
} ),
withSelect( function( select, props ) {
return {
metaValue: select( 'core/editor' ).getEditedPostAttribute( 'meta' )[ props.metaKey ]
}
} ) )( function( props ) {
const onUpdateImage = img => { props.setMetaValue( img.url ); };
return (
<img className='og-image__button' src={ props.metaValue } alt="" />
);
}
);
export default OGImage;
My optimal solution would be to do something like this, but this just stores the first value specified (so in this case, [img.url]):
const onUpdateImage = img => { props.setMetaValue( img.url, img.id ); };
...
<img className='og-image__button' src={ props.metaValue[0] } alt="" />
or even:
const onUpdateImage = img => { props.setMetaValue( [img.url, img.id] ); };
...
<img className='og-image__button' src={ props.metaValue[0] } alt="" />
I'm declaring the meta value as:
register_meta(
'post',
'_og_image',
[
'show_in_rest' => true,
'single' => false,
'type' => 'string'
]
);
As a last ditch effort, I'm considering combining both the Image URL and ID in a string and parsing it out on both ends. This solution feels hacky to me and I would prefer to store the two values independently as an array.
This is a crosspost.
Appendix for Tom. This worked once -- out of some strange fluke I'm assuming -- but I'm unable to reproduce it. The image url is being changed, but the image id refuses to save in the meta value.
PHP
function set_og_image() {
register_meta(
'post',
'og_image_url',
[
'show_in_rest' => true,
'single' => true,
'type' => 'string'
]
);
register_meta(
'post',
'og_image_id',
[
'show_in_rest' => true,
'single' => true,
'type' => 'string' // Solution (thank you to Tom): This needs to be an integer type.
]
);
}
JS
const OGImage = compose(
withDispatch( function( dispatch, props ) {
return {
setMetaValue: function( metaKey, metaValue ) {
dispatch( 'core/editor' ).editPost(
{ meta: { [ metaKey ]: metaValue } }
);
}
}
} ),
withSelect( function( select, props ) {
return {
metaValueURL: select( 'core/editor' ).getEditedPostAttribute( 'meta' )[ 'og_image_url' ],
metaValueID: select( 'core/editor' ).getEditedPostAttribute( 'meta' )[ 'og_image_id' ]
}
} ) )( function( props ) {
const onUpdateImage = img => {
props.setMetaValue( 'og_image_url', img.url );
props.setMetaValue( 'og_image_id', img.id );
};
const onRemoveImage = () => {
props.setMetaValue( props.metaKeyURL, null );
props.setMetaValue( props.metaKeyID, null );
};
return (
...
);
}
);
Parent Component:
const { __ } = wp.i18n;
const { Fragment } = wp.element;
const { PanelBody, PanelRow } = wp.components;
const { registerPlugin } = wp.plugins;
const { PluginSidebar, PluginSidebarMoreMenuItem } = wp.editPost;
import OGImage from './components/og-image';
const GenericSidebar = props => {
return (
<Fragment>
<PluginSidebarMoreMenuItem target="generic-sidebar">
{__("Generic Sidebar", "atlantis")}
</PluginSidebarMoreMenuItem>
<PluginSidebar
name="generic-sidebar"
title={__("Generic Sidebar", "atlantis")}
>
<PanelBody title={__("OG Image", "atlantis")} >
<PanelRow>
<OGImage
metaKeyURL = { 'og_image_url' }
metaKeyID = { 'og_image_id' }
/>
</PanelRow>
</PanelBody>
</PluginSidebar>
</Fragment>
);
};
registerPlugin("generic-sidebar", {
icon: "visibility",
render: GenericSidebar
});
registerPlugin()call to create a generic sidebar. I'll update the post again with code.