I'm entirely new to React and JSX and have been working on a select menu dropdown field within the WP sidebar. This select menu shows the currently added post categories, and that part is working perfectly.
Where I come unstuck is how to save this selection and how to update a post meta field with the selected option from the select control area.
I have created a post meta field using register_meta in a functions.php file:
<?php
register_meta( 'post', '_myprefix_text_metafield', array(
'show_in_rest' => true,
'type' => 'string',
'single' => true,
'sanitize_callback' => 'sanitize_text_field',
'auth_callback' => function ()
{
return current_user_can( 'edit_posts' );
}
) );
I then have an index.js file with the following script:
/**
* https://developer.wordpress.org/block-editor/reference-guides/packages/packages-plugins/#registerplugin
* https://developer.wordpress.org/block-editor/reference-guides/components/select-control/#usage-2
*/
(function (wp) {
const { registerPlugin } = wp.plugins;
const { SelectControl } = wp.components;
const { PluginDocumentSettingPanel } = wp.editPost;
const { useSelect } = wp.data;
const { useState } = wp.element;
registerPlugin('spectator-primary-category', {
render: function () {
const { categories } = useSelect((select) => {
const { getEntityRecords } = select('core');
return {
categories: getEntityRecords('taxonomy', 'category'),
}
})
let [selectedCategory, setSelectedCategory] = useState([]);
let options = [];
if (categories) {
options = categories.map(category => ({ label: category.name, value: category.id }));
options.unshift({ value: 0, label: 'Select a category' });
console.log(options);
}
return (
<PluginDocumentSettingPanel
name="spectator-primary-category-panel"
title="Add the primary category"
className="spectator-primary-category__panel"
>
<SelectControl
value={selectedCategory}
options={options}
onChange={(tokens) => setSelectedCategory(tokens)}
/>
</PluginDocumentSettingPanel>
)
},
icon: 'airplane' // or false if you do not need an icon
});
})(window.wp);
I have spent over 8 hours looking through the internet but haven't been able to achieve what I need. Would any awesome person be able to show me how to save this data, please?
=== Update ===
Thanks to @kofiegan for their help understanding how this works, I was able to update the meta field and display the results. Here is the final code used:
/**
* https://developer.wordpress.org/block-editor/reference-guides/packages/packages-plugins/#registerplugin
* https://developer.wordpress.org/block-editor/reference-guides/components/select-control/#usage-2
* https://stackoverflow.com/questions/75816358/gutenberg-save-data-to-post-meta-field-when-selectcontrol-is-changed
*/
(function (wp) {
const { registerPlugin } = wp.plugins;
const { SelectControl } = wp.components;
const { PluginDocumentSettingPanel } = wp.editPost;
const { useSelect, useDispatch } = wp.data;
registerPlugin('spectator-primary-category', {
render: function () {
const postId = useSelect(
(select) => select('core/editor').getCurrentPostId(),
[]
);
const postType = useSelect(
(select) => {
return select('core/editor').getCurrentPostType();
}, []
);
let editPost = useDispatch('core/editor').editPost;
let savePost = useDispatch('core/editor').savePost;
let entityRecordEdits = useSelect(
(select) => select('core').getEditedEntityRecord('postType', postType, postId),
[]
);
let getEntityRecord = useSelect(
(select) => select('core/editor').getEditedPostAttribute('meta')
);
// console.log(getEntityRecord);
function handleChange(tokens) {
let metaEdits = {
meta: {
...entityRecordEdits.meta,
_spectator_primary_category_metafield: tokens
}
};
editPost(metaEdits); // Update the Core Data store (client).
savePost(metaEdits); // Update the WordPress database (server).
}
const { categories } = useSelect((select) => {
const { getEntityRecords } = select('core');
return {
categories: getEntityRecords('taxonomy', 'category'),
}
})
let options = [];
if (categories) {
options = categories.map(category => ({ label: category.name, value: category.id }));
options.unshift({ value: 0, label: 'Select a category' });
// console.log(options);
}
return (
<PluginDocumentSettingPanel
name="spectator-primary-category-panel"
title="Add the primary category"
className="spectator-primary-category__panel"
>
<SelectControl
value={getEntityRecord._spectator_primary_category_metafield}
options={options}
onChange={handleChange}
/>
</PluginDocumentSettingPanel>
)
},
icon: 'airplane' // or false if you do not need an icon
});
})(window.wp);
There are many options for saving meta data. The code snippets below suggest one approach.
Answer updated with Aaron's corrections. getEditedEntityRecord
should be used, not getEntityRecordEdits
. getEditedEntityRecord returns the data from the database merged with changes made in the client while getEntityRecordEdits only returns the changes made in the client.
getEditedEntityRecord is used to ensure that changes made to post meta fields that were saved to the WordPress Core Data data store (data storage in the client) are not lost when updates are made by handleChange
. getEntityRecord could have been used instead which would retrieve the latest values stored in the database (data storage on the server), but not the latest changes made in the client.
const { useSelect, useDispatch } = wp.data;
...
const postId = useSelect(
( select ) => select( 'core/editor' ).getCurrentPostId(),
[]
);
const postType = useSelect(
( select ) => {
return select( 'core/editor' ).getCurrentPostType();
}, []
);
let editPost = useDispatch( 'core/editor' ).editPost;
let savePost = useDispatch( 'core/editor' ).savePost;
let entityRecordEdits = useSelect(
( select ) => select( 'core' ).getEditedEntityRecord( 'postType', postType, postId ),
[]
);
function handleChange( tokens ) {
let metaEdits = {
meta: {
...entityRecordEdits.meta,
META_FIELD_NAME: tokens // Replace META_FIELD_NAME with the name of the desired meta field.
}
};
editPost( metaEdits ); // Update the Core Data store (client).
savePost( metaEdits ); // Update the WordPress database (server).
setSelectedCategory(tokens); // Missing from original answer.
}
...
let [selectedCategory, setSelectedCategory] = useState([]);
...
<SelectControl
value={selectedCategory}
options={options}
onChange={ handleChange }
/>
...
Depending on the desired page behavior, you may not need to call savePost
.
Calling editPost
(and not savePost
) updates the WordPress Core Data store. Color changes in the page's Update button give the user feedback that unsaved changes are on the page. You could leave it up to the user to click Update to save your WP sidebar changes along with any other changes made by the user.
Calling savePost
immediately saves your WP sidebar changes along with any other changes to Post meta made by the user (e.g. from other page form fields that update meta). However, if WP sidebar updates to the server should not include other changes made to Post meta, then getEntityRecord
should be used rather than getEditedEntityRecord
.