Skip to main content

Create a settings page in WordPress with React – Part 2

Published ago
Updated ago
15 min read

In the first part of this tutorial, I showed the basic principles to create a settings page in WordPress, in this second part, I will show you how to use the components included in WordPress to add text fields, and save the options.

Table of Contents

Warning

You may need to install some packages for this tutorial. Although thanks to our compiler in @wordpress/scripts that automatically adds them to the dependencies of the script it may not be necessary, I prefer to install them simply to have a record of them.

If you happen to have an error simply run npm install --save-dev {package} with the following packages:

  • @wordpress/components
  • @wordpress/element
  • @wordpress/i18n

Step 1: Creating our application

To create a settings page, we need to add some fields. To start, we must open the settings.js file where we will start our application, and create a simple one:

settings.js

1/**
2 * WordPress dependencies
3 */
4import { createRoot } from '@wordpress/element';
5
6/**
7 * Internal dependencies
8 */
9import Settings from './components/settings';
10
11const rootElement = document.querySelector('#mah-settings');
12
13if (rootElement) {
14 createRoot(rootElement).render(<Settings />);
15}
16

components/settings.js

1/**
2 * WordPress dependencies
3 */
4import { __ } from '@wordpress/i18n';
5
6const Settings = () => {
7 return <h1>{__('Select the desired options', 'mah-settings')}</h1>;
8};
9
10export default Settings;
11

Among the things we can observe in this piece of code are:

  1. The use of createRoot with which we start our applications in React, but unlike how it is normally used, this time we take it from @wordpress/element. This is because WordPress already comes with its own copy of React that ensures that its components will work.
  2. Likewise, it comes with a javascript version of the __ function for translations, unlike PHP there is no version of esc_html__ so we use the normal version.
Caution

Using React

As you can see from this small piece of code (and the title of the post...). This tutorial assumes that you have at least a basic knowledge of ReactJS, if not; I invite you to study it a bit so you don't lose the thread of this topic.

Step 2: Add some options

Now that we have our application running we can start to add options to our page, one of the most common I come across is text fields, perhaps for an API, for example:

1/**
2 * WordPress dependencies
3 */
4import { Card, CardBody, TextControl } from '@wordpress/components';
5import { useState } from '@wordpress/element';
6import { __ } from '@wordpress/i18n';
7
8const Settings = () => {
9 const [apiKey, setApiKey] = useState('');
10
11 return (
12 <>
13 <h1>{__('Select the desired options', 'mah-settings')}</h1>
14 <Card>
15 <CardBody>
16 <TextControl
17 help={__('Enter your API key', 'mah-settings')}
18 label={__('API Key', 'mah-settings')}
19 onChange={setApiKey}
20 value={apiKey}
21 />
22 </CardBody>
23 </Card>
24 </>
25 );
26};
27
28export default Settings;
29

After reloading the page, we can see that our options page takes shape! although we can add some flavor, but we will do that later, now that we have our field, let's explain what happens here:

  1. Some more WordPress components are added, this time they are Card, CardBody, and TextControl. In reality the important one is TextControl since it is the one we will use to enter our options
  2. We import useState from @wordpress/element (once again, React comes included)
  3. We add an empty state that will be updated when we change something in TextControl
  4. The use of React fragments, only that we use its shortened version, instead of import { Fragment } from '@wordpress/element' we simply use <></>
Options page with text field
Tip

@wordpress/components comes with many components available, I suggest checking the Storybook to see a demonstration of what we have at our disposal

As a bonus, let's add a toggle simply because I like how they work:

1/**
2 * WordPress dependencies
3 */
4import {
5 Card,
6 CardBody,
7 CardDivider,
8 TextControl,
9 ToggleControl,
10} from '@wordpress/components';
11import { useState } from '@wordpress/element';
12import { __ } from '@wordpress/i18n';
13
14const Settings = () => {
15 const [ apiKey, setApiKey ] = useState( '' );
16 const [ toggled, setToggled ] = useState( false );
17
18 return (
19 <>
20 <h1>{ **( 'Select the desired options', 'mah-settings' ) }</h1>
21 <Card>
22 <CardBody>
23 <TextControl
24 help={ **( 'Enter your API key', 'mah-settings' ) }
25 label={ __( 'API Key', 'mah-settings' ) }
26 onChange={ setApiKey }
27 value={ apiKey }
28 />
29 </CardBody>
30 <CardDivider />
31 <CardBody>
32 <ToggleControl
33 label={ __( 'Activate functionality', 'mah-settings' ) }
34 onChange={ setToggled }
35 checked={ toggled }
36 />
37 </CardBody>
38 </Card>
39 </>
40 );
41}
42export default Settings;
43

What happened?

If we reload the page, we realize that our toggle looks more like a checkbox, what happened?

Toggle as checkbox

What happens is that despite our javascript dependencies being automatically added to our script, the same does not happen with our style dependencies. So it may be necessary to add them ourselves to the file class-mah-settings.php in a similar way to what we did with the javascript script:

class-mah-settings.php

1/**
2 * Registra los scripts y estilos.
3 *
4 * @return void
5 */
6public function enqueue_scripts(): void {
7 $screen = get_current_screen();
8
9 if ( $screen->id !== $this->menu_slug ) {
10 return;
11 }
12
13 $asset = include $this->plugin_path . '/build/settings.asset.php';
14
15 wp_enqueue_script(
16 'mah-settings',
17 plugins_url() . '/mah-settings-react/build/settings.js',
18 $asset['dependencies'],
19 $asset['version'],
20 true
21 );
22
23 wp_enqueue_style( 'wp-components' );
24}
25

When we reload the page we will see the desired result, and if we look closely, our text field also improved in appearance:

Options page with text field and toggle

Step 3: Register options in the backend

To be able to save our options, we need to have values in the database where to save them, WordPress basically handles the following types:

  • Theme mods1
  • Post meta22
  • Site Options33

In our case we will use global options, due to the nature of our options page.

In php, we can register them like this:

php/class-mah-settings.php

1/**
2 * Registra los hooks.
3 *
4 * @return void
5 */
6public function init(): void {
7 add_action( 'admin_menu', [ $this, 'add_menu_page' ] );
8 add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] );
9 add_action( 'admin_init', [ $this, 'register_settings' ] );
10 add_action( 'rest_api_init', [ $this, 'register_settings' ] );
11}
12

php/class-mah-settings.php

1/**
2 * Registra las opciones.
3 *
4 * @return void
5 */
6public function register_settings(): void {
7 register_setting(
8 'mah_settings',
9 'mah_api_key',
10 [
11 'description' => __( 'API Key', 'mah-settings' ),
12 'sanitize_callback' => 'sanitize_text_field',
13 'show_in_rest' => true,
14 'type' => 'string',
15 ]
16 );
17 register_setting(
18 'mah_settings',
19 'mah_function',
20 [
21 'description' => __( 'Funcion X', 'mah-settings' ),
22 'show_in_rest' => true,
23 'type' => 'boolean',
24 ]
25 );
26}
27
Warning

The second rest_api_init hook is necessary to be able to use our options in React, otherwise show_in_rest would have no effect

Step 4: Add save button with React

Now we just need to save our options from the React side, for which we need to add a save button to our application, and add an event when it is pressed:

1/**
2 * WordPress dependencies
3 */
4import {
5 Button,
6 Card,
7 CardBody,
8 CardDivider,
9 CardFooter,
10 TextControl,
11 ToggleControl,
12} from '@wordpress/components';
13import { useState } from '@wordpress/element';
14import { __ } from '@wordpress/i18n';
15
16const Settings = () => {
17 const [apiKey, setApiKey] = useState('');
18 const [toggled, setToggled] = useState(false);
19 const saveSettings = () => {
20 console.log('Save');
21 };
22 return (
23 <>
24 <h1>{__('Select the desired options', 'mah-settings')}</h1>
25 <Card>
26 <CardBody>
27 <TextControl
28 help={__('Enter your API key', 'mah-settings')}
29 label={__('API Key', 'mah-settings')}
30 onChange={setApiKey}
31 value={apiKey}
32 />
33 </CardBody>
34 <CardDivider />
35 <CardBody>
36 <ToggleControl
37 label={__('Activate functionality', 'mah-settings')}
38 onChange={setToggled}
39 checked={toggled}
40 />
41 </CardBody>
42 <CardFooter>
43 <Button disabled={!apiKey} onClick={saveSettings} variant='primary'>
44 {__('Save', 'mah-settings')}
45 </Button>
46 </CardFooter>
47 </Card>
48 </>
49 );
50};
51export default Settings;
52

A brief explanation of what we just added:

  1. We bring a new component CardFooter, which will help us maintain the style of our card
  2. We bring the Button component that comes with WordPress, this gives us some advantages over using regular buttons, such as the fact that it already comes with styles for example, this time we use the primary variant. And we make sure that it remains disabled if the API key field has not been filled
  3. We add the saveSettings function that will be in charge of saving our options

So when we click the button we will see how our action is registered in the console, now we just need to replace it with the real save function.

Options page with text field, toggle, and save button

Coming soon…

My original idea was to finish the tutorial in this part, but I realized how long the post is becoming and there are still quite a few concepts I want to touch on, so I have decided that this is a good time to finish this post and return next week with a third part where we will save our options, finish creating a settings page; and we will see concepts like:

  • The use of useDispatch, useEntityProp, and saveEditedEntityRecord
  • UX improvements for our page
  • General introduction to the @wordpress/data package and the use of stores.

In the meantime you can check the code of this second part in my repository.

As always your comments and suggestions on what you would like to see explained in this humble blog are welcome. Until next time!

Read more

Footnotes

  1. Very popular in the days of customizer, theme mods are tied to the theme used on our site, if the theme changes, the mods also disappear, but they return when the theme they are tied to is selected again. But with the arrival of FSE, these begin to be irrelevant in my opinion ↩︎

  2. Post meta, are basically the attributes that are tied to a specific post, these can change from post to post ↩︎

  3. Used for global options that can be used by either a theme or a plugin, regardless of which post or theme is selected on the site ↩︎