Gutenberg / Block editor: Dynamically populate SelectControl, RadioControl or CheckboxControl options

If you have some experience with Gutenberg development, It’s easy enough to create a simple control that lets a user select one or more options, like this:

<RadioControl
  label="Align"
  help="The type of the current user"
  selected={ postMeta._my_selection }
  options={ [
    { label: 'Left', value: 'left' },
    { label: 'Center', value: 'center' },
    { label: 'Right', value: 'right' },
  ] }
  onChange={ ( value ) => setPostMeta( { _my_selection: value } ) }
/>

In this example we save the selected option to and load it from post meta. We use withSelect and withDispatch for this:

withSelect( ( select ) => {		
  return {
      postMeta: select( 'core/editor' ).getEditedPostAttribute( 'meta' ),
  };
} ),
withDispatch( ( dispatch ) => {
  return {
    setPostMeta( newMeta ) {
      dispatch( 'core/editor' ).editPost( { meta: newMeta } );
    }
};
} )

and we also need to register the meta fields in PHP with register_post_meta.

If you want to learn more about this part, I recommend you to check out this tutorial.

Load options from the wp.data store

But what if you need to to get the possible options from a database? For example, you want to create a select field that allows your user to pick a related post type (for whatever reason)?

While slightly more tricky, it’s still easy enough to achieve by getting the required info from wp.data:

Add this to your main JS function:

<RadioControl
  label="Align"
  help="The type of the current user"
  selected={ postMeta._my_selection }
  options={ options={ ( postTypes ?? []).map( pt => (
    { label: pt.name, value: pt.slug }
  ) ) } }
  onChange={ ( value ) => setPostMeta( { _my_selection: value } ) }
/>

Add this to your withSelect:

postTypes: select('core').getPostTypes()

Don’t forget to add postTypes to your main function parameters:

const My_Main_Function = ( { postType, postMeta, setPostMeta, postTypes } ) => {

Load options from PHP array

All well and good, but what if you have some kind of dataset that you can access only via PHP?
For example, an array like this:

<?php
$my_options = [
  ['label' => 'Option one', 'value' => 'o1'],
  ['label' => 'Option two', 'value' => 'o2'],
  ['label' => 'Option three', 'value' => 'o3'],
];

If we want to show these options in the block editor, we can create our own custom REST endpoint first:

<?php
add_action('rest_api_init', function() {
  register_rest_route('xyz/v1', '/my-options', [
    'method' => 'GET',
    'callback' => function(){
      $my_options = [
        ['label' => 'Option one', 'value' => 'o1'],
        ['label' => 'Option two', 'value' => 'o2'],
        ['label' => 'Option three', 'value' => 'o3'],
      ];
      return rest_ensure_response($my_options);
    },
    'permission_callback' => function() {
      return current_user_can( 'edit_posts' );
    },
  ]);
});

Then on the javascript side, we can add this rest route to the wp.data store, like so:

dispatch( 'core' ).addEntities( [
    {
        name: 'my-options', // route name
        kind: 'xyz/v1', // namespace
        baseURL: '/xyz/v1/my-options', // API path without /wp-json
    }
]);

Now we can add this to the object that withSelect returns:

myOptions: select( 'core' ).getEntityRecords( 'xyz/v1', 'my-options' )

And after adding myOptions to the main function parameters, we can simply do:

<RadioControl
  label="My options"
  selected={ postMeta._my_selected_option }
  options={ options={ ( myOptions ?? []).map( o => (
    { label: o.label, value: o.value }
  ) ) } }
  onChange={ ( value ) => setPostMeta( { _my_selected_option: value } ) }
/>

Note that you will also need to register _my_selected_option as post meta to remember which option was actually selected.

Instead of a RadioControl, you could use a SelectControl or CheckboxControl.