Introduction

WordPress is the most popular content management system (CMS) with millions of users. It's a great platform for building a blog, or any other type of website. Of course, it's not a silver-bullet solution, and it also has limitations.

Throughout my career as a web developer, I've developed numerous WordPress plugins with TypeScript, Vue and React. That's one of the reasons why I've decided to write this article to show you the code structure that I use for every plugin.

That's precisely what we set out to do, I'll walk you through the process of creating a custom WordPress plugin, with OOP on the backend and TypeScript on the frontend.

First, we'll install WordPress with Docker. Then we'll create a backend setup for our plugin, and create a communication between our frontend and backend. It's going to be interesting.

It doesn't matter if you already have experience developing plugins for WordPress or if you're just getting started — this guide will be really helpful for you.

Likewise, it's worth mentioning up-front that I love types everywhere in my code. I will not use any static analyzation tools for PHP, just because of simplicity of the article. However, I will still include types everywhere I can in PHP, just because I always do that. It helps PHP Storm and other IDEs to better understand the code and to make it easier to debug in the future.

Foreword

I am not a WordPress expert by any means. Likewise, I have never followed the official WordPress developing guidelines, so I won't use framework/plugin boilerplate here. Instead, we'll start with a code structure that I use for every plugin.

On the frontend, we will use React's JSX syntax. On the backend, we will use Object-Oriented Programming (OOP). Although, OOP is not common in WordPress projects, I still prefer to do it this way because it allows me to create a plugin that is much easier to maintain and extend in the future.

I'm not going to use recommended WordPress code style because I've never used it before, and I don't feel comfortable to use it in PHP. Therefore, I'll do my coding in PSR12 because it's used in modern PHP projects.

Stack for building a WordPress plugin with React
Technologies we are going to use in this tutorial

WordPress setup

For plugin development, I'm going to use Docker. If you don't know Docker, or you avoid using it for some reason, just skip this section and head over to wordpress.org/download, and install WordPress manually on your computer. However, I recommend using a Docker container for development purposes because installing everything manually is harder.

I will install a fresh WordPress instance on my local machine using a Docker image containing the latest stable version of PHP, MariaDB, and Composer dependency management tool.

Here is the directory structure which I will use for this project:

Directory structure for current project

All the source code is available on GitHub by this link: github.com/SerhiiChoBlog/wordpress-react.

Here is a docker-compose.yml file:

version: '3.1'

services:
    wp:
        build:
            context: ./wp
        container_name: wp
        restart: always
        ports:
            - 8080:80
        depends_on:
            - db
        volumes:
            - ./wp/src:/var/www/html
        environment:
            WORDPRESS_DB_HOST: db
            WORDPRESS_DB_USER: user
            WORDPRESS_DB_PASSWORD: 111111
            WORDPRESS_DB_NAME: wp_react
            WORDPRESS_DEBUG: 1

    db:
        image: mariadb:10.7.3-focal
        command: --default-authentication-plugin=mysql_native_password
        container_name: db
        restart: always
        ports:
          - 2222:3306
        volumes:
            - ./db:/var/lib/mysql
        environment:
            MYSQL_DATABASE: wp_react
            MYSQL_USER: user
            MYSQL_PASSWORD: 111111
            MYSQL_ROOT_PASSWORD: 111111

Dockerfile is pretty simple, we need it just for installing composer into our Docker image.

FROM wordpress:6.0-php8.0-apache
COPY --from=composer:latest /usr/bin/composer /usr/local/bin/composer

Also, I added .dockerignore file and include /.git and /db directories into it. The content of these files you can find on GitHub if you need it.

That's just about it for the Docker setup. This is precisely why developers love Docker because it allows them to build complex environments locally by just running a single command. In our case, the command is: docker-compose up -d.

Installing WordPress

After the containers are started and running, I visit localhost:8080 URL to view the default WordPress installation page. As you can see on the screen, WordPress is installed and ready. It might take some time depending on your computer.

Default WordPress welcome page

We follow the next step and choosing the language setting. After clicking on “Continue”, WordPress will show the installation's main page, which requires filling out some basic information.

WordPress installation page

I click “Install WordPress” button and login into WordPress admin panel. Now my WP admin dashboard is ready for work!

WordPress dashboard

Plugin boilerplate

Before diving into the implementation details, let's create our plugin boilerplate. By boilerplate, I mean the basic structure that I use for every WordPress plugin. I follow this structure most of the time for my plugins, and I use them whenever I need to reuse some functionality.

As I mentioned above, this structure is pretty standard in every plugin that I write, even though it's not perfect. I usually have to make some adjustments to make it work well for each particular plugin, but you can always modify it to meet your specific requirements.

Anyway, I will create a wp-react directory in wp/src/wp-content/plugins directory. As you probably know, the plugins directory is where all the WordPress plugins are located.

WordPress plugin is located in plugins directory

My first file in the plugin directory is always called the same way as the plugin directory. For this case, my file is wp-react.php because the plugin directory is wp-react. Let's take a look what the file looks like:

<?php

declare(strict_types=1);

/*
Plugin Name: WP React
Author: Serhii Cho
Author URI: https://serhii.io
Description: Test plugin for showing how to make a plugin with React JS and TypeScript
Version: 0.1
License: no
License URI: https://serhii.io
Text Domain: wpreact
Tags: react, typescript, tutorial, test-plugin, psr12
*/

defined('ABSPATH') || exit;
define('WPREACT_PATH', plugin_dir_path(__FILE__));
define('WPREACT_URL', plugin_dir_url(__FILE__));

This comment provides information about the plugin author and plugin version. You can add any additional information you want here. For example, you can use a license for your plugin.

Under the comment, I typically define constants which will be used in the plugin.

ABSPATH ⁣— is defined by WordPress, so you don't need to define it manually. It is defined as an absolute path based on the location of the plugin directory. When it's not defined, it means that you are developing your plugin outside the WordPress installation. Don't bother outputting any error message in this case, just exit.

WPREACT_PATH — is the absolute path to the plugin directory which I will use in my code (in this case it is pointing to /wp-content/plugins/wp-react directory).

WPREACT_URL — is the generated URL for the plugin's directory. This URL will be used to create links to this plugin from the frontend.

Hooks

WordPress allows plugin authors to add custom functionality to certain places in WordPress itself. This can be achieved with hooks. To read about hooks in WordPress, go to the documentation page. I highly recommend reading this page as it will help you avoid making mistakes when writing your plugins.

After creating the first file, I add a Hook.php file. This file contains all the necessary logic for our plugin. It will be placed in the app directory.

This is how the file currently looks:

<?php

declare(strict_types=1);

namespace WpReact;

final class Hook
{
    public function init(): void
    {
        foreach (get_class_methods($this) as $method) {
            if ($method !== 'init') {
                $this->{$method}();
            }
        }
    }
}

It takes all the method names from this class with function get_class_methods, and executes each one except init method. What is important here is that it will execute methods in the same order as you write it, from the top to bottom.

Before moving any further, we should do a couple of things. Firstly, we need to add composer autoloading.

cd wp/src/wp-content/plugins/wp-react && composer init

After answering composer questions, I get the composer.json file with this content:

{
    "name": "serhiichornenkyi/wp-react",
    "require-dev": {},
    "autoload": {
        "psr-4": {
            "WpReact\\": "app/"
        }
    },
    "authors": [
        {
            "name": "SerhiiCho",
            "email": "serhiicho@protonmail.com"
        }
    ],
    "require": {}
}

The namespace “WpReact” will be bound to the app directory of our plugin. With composer autoloading in place, we can go back to the wp-react.php file and execute the init function. To the end of this file, after defining constants, I'll add these two lines:

require_once 'vendor/autoload.php';

(new WpReact\Hook())->init();

Now, every method defined in Hook class will be automatically executed. If you get any errors, try running composer dumpautoload. It might resolve your problem.

Don't worry if you don't understand why we need a Hook class, it will make sense after we register WordPress shortcodes and assets. Let's do it right after we enable our plugin in WordPress admin panel.

Navigate to “Plugins” menu and enable our WP React plugin there by clicking “Activate” button. Once it's done, we can add our first shortcode.

WordPress shortcodes are PHP functions that let you add some special content to a website pages or posts using a simple syntax, like this: [this_is_my_shortcode]. You simply insert this shortcode into a page, and it will generate the content you specified in the code.

In our Hook class, I'll add a new private method registerBirdsShortcode. This shortcode will generate a list of birds on our frontend page.

private function registerBirdsShortcode(): void
{
    add_shortcode('wpreact_birds', static function (): string {
        add_action('wp_enqueue_scripts', function () {
            $path = WPREACT_PATH . 'assets/birds.js';
            $url = WPREACT_URL . 'assets/birds.js';

            wp_register_script('wpreact-birds-js', $url, [], $path);

            wp_localize_script('wpreact-birds-js', 'wpReactBirdsGlobals', [
                'ajaxUrl' => admin_url('admin-ajax.php'),
                'nonce' => wp_create_nonce('wpreact-birds'),
            ]);

            wp_enqueue_script('wpreact-birds-js');
        });

        return 'Birds are cute 🕊';
    });
}

There is a lot of action going on, but it's pretty straightforward. First we register a shortcode [wpreact_birds] and passing a callback function that is going to return a content of the shortcode. In our case, our shortcode is simply a string: Birds are cute 🕊.

The add_action function allows you to execute a passed callback function at specific points during WordPress execution. In our case, the action name is wp_enqueue_scripts, from the WordPress documentation we can read that it will execute a passed callback when scripts and styles are enqueued.

We can call add_action function anywhere inside our WordPress plugin, but in our case we would like to register JavaScript only when shortcode is loaded into the page. We aim to avoid loading JS on all the pages on the site for better performance.

Another thing I want you to notice, is that a wp_localize_script function registers a global JavaScript object wpReactBirdsGlobals with 2 properties, ajaxUrl and nonce. If you ever need to pass some public value from backend to frontend, you can do it with this array.

To check if our code works, let's create an assets/birds.js file in our plugin root with console.log('Works!') inside the file. Let's see if that message is displayed on the console when we load the page with shortcode: [wpreact_first]. For that, we need to create a new page and insert a shortcode in it.

In WordPress admin panel, navigate to the Pages section and click Add New. Give your new page a title and a shortcode [wpreact_birds].

Insert a shortcode into a WordPress page

Your interface may differ from mine because you might use a different WordPress version or have a different editor installed. Click Publish button and navigate to a newly created page. Open the console, you should see a message: “Works!”, and the page content: “This is the first shortcode”. That means our code works as expected.

Testing JavaScript on the frontend

Since our JavaScript loads, we can now install React and compile it into the plain JavaScript, which will be generated in a birds.js file by our compiler. Let's do it right now and install all the required dependencies.

Install React

There are few ways to compile React to a plain JavaScript, since this article is about my personal workflow I prefer using a wrapper around the Webpack called: Laravel Mix. It is easy to install and even easier to configure.

I'm going to run this command to create a package.json file and install Laravel Mix and everything else that we need. First, we'll enter the plugin directory via terminal command cd wp/src/wp-content/plugins/wp-react and install all we need by running this command:

yarn init -y \
    && yarn add laravel-mix @types/react @types/react-dom --dev \
    && yarn add react react-dom axios

Great! To configure Laravel Mix, we need to create a webpack.mix.js file in the root directory and configure Webpack. Here is how I set up the configuration file:

const mix = require('laravel-mix')

mix.ts('resources/ts/birds/main.tsx', 'assets/birds.js')
    .react()
    .sourceMaps()
    .disableNotifications()

You can see why I prefer using Laravel Mix over other options. It allows us to use all the power of Webpack with nice configurations and great community support. I love how readable it is and how easy it is to remember. Let's do the last step and create tsconfig.json file by running tsc --init.

If you don't have TypeScript installed on your machine, create tsconfig.json manually with this JSON:

{
  "compilerOptions": {
    "target": "es2016",
    "module": "commonjs",
    "jsx": "react",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true
  }
}

The only line which I added manually is "jsx": "react", everything else is generated by running the tsc --init.

I will also add 2 scripts to a package.json file to run Laravel Mix commands. This is how my package.json file looks like:

{
  "name": "wp-react",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "devDependencies": {
    "laravel-mix": "^6.0.49"
  },
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  },
  "scripts": {
    "watch": "mix watch",
    "prod": "mix --production"
  }
}

Now, we have everything in place to compile React JSX files to plain JavaScript and save them into the assets directory of our plugin. I can now run yarn watch or npm run watch to ask Webpack to watch changes in our resources/ts/birds directory.

Let's modify the main.tsx file to React instantiation and see if it compiles.

import React from "react"
import { render } from "react-dom"
import { createRoot } from 'react-dom/client'

window.addEventListener('load', () => {
    const target = document.getElementById('wpreact-birds')

    if (!target) {
        throw new Error('Cannot find element #wpreact-birds')
    }

    const root = createRoot(target)

    root.render(<h2>Birds are cute creatures</h2>)
})

We can clearly see that when the page is loaded, we create a React instance, and render JSX with h2 tag into a #wpreact-birds element. Since we don't have an element with this id attribute, let's make it so our shortcode generates it.

So, in our registerBirdsShortcode() method, I'll change the return statement to this:

return '<div id="wpreact-birds"></div>';

Run yarn watch if you haven't already ran this command and check the Birds page to see if our shortcode changes to a React app. In my case, the Birds page looks like this:

WordPress page with React app

If you don't get this result, make sure your yarn watch command is running. Occasionally, you need to run it twice because it installs additional dependencies.

Congratulations! You have successfully created a WordPress plugin with TypeScript and React. Of course, we are nowhere near done with our plugin yet, but the base is already there, and we can focus on getting data from the server and displaying it on the frontend.

Fetch birds from the server

Before we do anything else, let's create an App.tsx file in our resources/ts/birds directory, which will be our main parent component.

import React from 'react'

const App: React.FC = () => {
    return (
        <h2>Birds are cute creatures</h2>
    )
}

export default App

Now, we can go back to the main.tsx, and import App.tsx there with strict mode enabled.

import React from "react"
import { render } from "react-dom"
import { createRoot } from 'react-dom/client'
import App from './App'

window.addEventListener('load', () => {
    const target = document.getElementById('wpreact-birds')

    if (!target) {
        throw new Error('Cannot find element #wpreact-birds')
    }

    const root = createRoot(target)

    root.render(
        <React.StrictMode>
            <App />
        </React.StrictMode>
    )
})

That's mostly how you would do in a normal React app. Now, we can finally create the Birds.tsx component and make it fetch data from the server. I'm going to create a components/Birds/Birds.tsx file.

For now, before we start fetching data from the server, let's create the frontend first. Here is the Birds.tsx file with some temporary data:

import React from 'react'
import './Birds.css'

const Birds: React.FC = () => {
    return (
        <ul className="Birds-list">
            <li className="Birds-item">
                <img
                    src="https://i.imgur.com/GJKdVfL.jpeg"
                    alt="Bird looking right"
                />
                <h2>Bird looking right</h2>
            </li>
            <li className="Birds-item">
                <img
                    src="https://i.imgur.com/1lC07Hh.jpeg"
                    alt="Cute sparrow bird"
                />
                <h2>Cute sparrow bird</h2>
            </li>
        </ul>
    )
}

export default Birds

There is also a Birds.css file imported at the top. I'm not going to include any CSS in this article, but here is the link to the Birds.css file on GitHub if you need it. To see the result in the browser, I will also import the Birds component in the App.tsx file like this:

import React from 'react'
import Birds from './components/Birds/Birds'

const App: React.FC = () => {
    return (
        <Birds />
    )
}

export default App

On our frontend page, we can see the result:

WordPress example page with 2 birds

Our component is properly displayed without errors. If you get any, try to follow all the steps and see what you did wrong. Let's move on and create a backend which will give us the array of birds. We'll use this data to render it on the frontend.

Backend

All we need from the backend functionality is to get data which will be used on our frontend. I'm going to start from creating the app/Http/Ajax/AjaxEntry.php file. As the name of the file suggests, this class will receive the request from the frontend and point this request to a certain handler. A handler is a class, that will process the request and return the response back to the frontend.

This is how the AjaxEntry.php file looks like:

<?php

declare(strict_types=1);

namespace WpReact\Http\Ajax;

use JsonException;
use Throwable;
use WpReact\Http\Ajax\Handlers\GetBirdsHandler;
use WpReact\Http\Ajax\Handlers\Handler;
use WpReact\Http\Ajax\Requests\GetBirdsRequest;

final class AjaxEntry
{
    /**
     * Determine what method need to call when AJAX request comes.
     * Each action name points to a handler, which will be executed
     * when the request will be received.
     *
     * @return array<string, callable(): Handler>
     */
    public function mapActionsToHandlers(): array
    {
        return [];
    }

    /**
     * @throws JsonException
     */
    public function openEntry(): void
    {
        foreach ($this->mapActionsToHandlers() as $action_name => $callback) {
            $action = function () use ($callback): void {
                check_ajax_referer('wpreact-birds');

                try {
                    echo $callback()->handle();
                } catch (Throwable $e) {
                    echo Response::jsonError($e->getMessage());
                }

                wp_die();
            };

            add_action("wp_ajax_$action_name", $action);
            add_action("wp_ajax_nopriv_$action_name", $action);
        }
    }
}

The mapActionsToHandlers method just returns the array of callbacks, each callback handles the particular request. In other words, every request will have a unique action name, in mapActionsToHandlers method we map each action name with a callback function, that will be executed after the request is received.

The openEntry method just assigns each callback function to the wp_ajax_* and wp_ajax_nopriv_* actions. It's just the way of registering the AJAX endpoint in WordPress. Every POST request to this endpoint will be automatically processed by the corresponding callback. Adding wp_die() after AJAX callback will terminate the Ajax request so that we don't have to wait for the full response.

Of course, there are many ways of handling AJAX requests on the backend of your WordPress plugin. This is just one way that I, personally, like to implement it in my projects. I hope you find the above explanation useful. Let's create an AJAX handler so that you have a better understanding about how it works behind the scenes.

I will create an app/Http/Ajax/Handlers/GetBirdsHandler.php file. This class is responsible for processing the request and giving back the JSON data. Creating this handler also involves a couple of steps. Firstly, all AJAX handlers needs to have a common interface app/Http/Ajax/Handlers/Handler.php. This interface is just a simple way to make sure that all the handlers have a handle method which returns string. It acts as a wrapper for the actual function which will be executed when the request is received.

<?php

declare(strict_types=1);

namespace WpReact\Http\Ajax\Handlers;

interface Handler
{
    public function handle(): string;
}

Pretty simple, right? Now we can create a GetBirdsHandler class which will implement the Handler interface.

<?php

declare(strict_types=1);

namespace WpReact\Http\Ajax\Handlers;

use WpReact\Http\Ajax\Requests\GetBirdsRequest;
use WpReact\Http\Ajax\Response;

class GetBirdsHandler implements Handler
{
    public function __construct(private GetBirdsRequest $request)
    {
    }

    public function handle(): string
    {
        return '';
    }
}

You can see that we also have a GetBirdsRequest injected in the constructor. This is just a wrapper around our $_POST variable. This is what it looks like:

<?php

declare(strict_types=1);

namespace WpReact\Http\Ajax\Requests;

class GetBirdsRequest extends Request
{
    private const DEFAULT_BIRDS_LIMIT = 2;

    public function getBirdsLimit(): int
    {
        $limit = $this->data['birds_limit'] ?? self::DEFAULT_BIRDS_LIMIT;
        return (int) $limit;
    }
}

I use these request wrappers for each handler that needs a request. You see that I even give similar names to a handler and request so that it's easier to understand which request relates to each handler. In this case, a get_birds action corresponds to GetBirdsHandler which has an injected GetBirdsRequest class.

For some people, this whole logic seems a bit difficult to grasp. Why even make request wrappers and pass them into the handler when we can just use a global $_POST variable? Well, the problem is that you don't know what hides inside the $_POST variable. It means that you don't get any code completion or type errors when using request values. Moreover, request wrappers can hide some extra logic which can help you to clean up your code. All this makes sense after some time when your plugin grows bigger.

The Request class which is being extended by all our requests is pretty simple. This is what it looks like:

<?php

declare(strict_types=1);

namespace WpReact\Http\Ajax\Requests;

abstract class Request
{
    /**
     * @var array<string>
     */
    protected array $data;

    public function __construct()
    {
        $this->data = $_POST;
    }
}

The $data property is an array of strings which contains all the POST data received by the server. That's it, nothing fancy going on here.

For our simple example, we have too much logic here. However, let's say that we have a few handlers that need different request objects. All we have to do is, create a new request objects and pass it into the handler's constructor. The big advantage, is that we encapsulate request data into their objects. I do it in all WordPress plugins and I always fascinated how helpful it is.

Let's make so that this handler gives back the list of birds. For this, we already have a GetBirdsHandler, let's finish the handle method.

public function handle(): string
{
    $birds = [
        [
            'url' => 'https://i.imgur.com/GJKdVfL.jpeg',
            'title' => 'Bird looking right',
        ],
        [
            'url' => 'https://i.imgur.com/1lC07Hh.jpeg',
            'title' => 'Cute sparrow bird',
        ],
    ];

    $birds = array_splice($birds, 0, $this->request->getBirdsLimit());

    header('Content-Type: application/json');

    return json_encode($birds, JSON_THROW_ON_ERROR);
}

Before moving any further, I want to refactor this method because the last 2 lines are always going to be present when handler gives a JSON response. To prevent repeating myself in the future, I'll create a reusable method which will contain these last 2 lines from handle method.

I'll create a Response class in an app/Http/Ajax directory with the method json and jsonError. Here is the code:

<?php

declare(strict_types=1);

namespace WpReact\Http\Ajax;

use JsonException;

class Response
{
    /**
     * @throws JsonException
     */
    public static function json(array|object $data): string
    {
        header('Content-Type: application/json');

        return json_encode($data, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE);
    }

    /**
     * @throws JsonException
     */
    public static function jsonError(string $message): string
    {
        return self::json(['error' => WP_DEBUG ? $message : 'Server error']);
    }
}

The jsonError method is just a syntactic sugar to simplify a readability of the openEntry method. As for handler, we can now remove those last 2 lines from the handle method and return Response::json from it:

$birds = array_splice($birds, 0, $this->request->getBirdsLimit());

return Response::json($birds);

Our handle method looks much simpler now. We can now register our handler in the mapActionsToHandlers method like this:

public function mapActionsToHandlers(): array
{
    return [
        'get_birds' => function (): Handler {
            return new GetBirdsHandler(new GetBirdsRequest());
        },
    ];
}

The “get_birds” key here is the name of the action, the callback is something that will be executed when a POST request comes in with the parameter action: “get_birds”. This way, we know which callback to execute.

Let's do the last step for the backend part and move to the front end part of the plugin. In our final step for the backend part, we'll execute an openEntry method when WordPress initializes our plugin.

That's why we have the Hook class in place. I'll add a registerApi method to it right after the registerBirdsShortcode:

    /**
     * @throws JsonException
     */
    private function registerApi(): void
    {
        $ajax_entry = new \WpReact\Http\Ajax\AjaxEntry();
        $ajax_entry->openEntry();
    }

The registerApi method will be executed automatically by WordPress when the init method is called in our wp-react.php file. Now, our backend is ready to receive the requests from React.

AJAX requests

In almost all of my WordPress plugins, I always have the same JavaScript file that I copy and paste from one project to another with small changes. This file is called “Request.ts”. It basically contains the core method that I use for all my HTTP requests. This is what it looks like for this particular project:

import axios from 'axios'

export type RequestData = {
    method: string
    params?: string | { name: string, value: string | Blob }[]
}

export type RequestInterface = {
    send: () => Promise<any>
}

class Request<T> implements RequestInterface {
    private readonly data: RequestData

    public constructor(data: RequestData) {
        this.data = data
    }

    public send(): Promise<{ data: T }> {
        return axios.post(window.wpReactBirdsGlobals.ajaxUrl, this.createParams())
    }

    private createParams(): FormData {
        const params = new FormData()

        params.append('action', this.data.method)
        params.append('_ajax_nonce', window.wpReactBirdsGlobals.nonce)

        if (this.data.params) {
            if (typeof this.data.params === 'string') {
                params.append('data', this.data.params)
            } else {
                this.data.params.forEach(param => {
                    params.append(param.name, param.value)
                })
            }
        }

        return params
    }
}

export default Request

If you value your time like myself, you understand the concept of abstraction. Abstraction is, roughly speaking, the process of bundling complex or repetitive tasks into a simplified form that is easy to reuse and simplify later on. This is undoubtedly what Request.ts file does for us. It abstracts things that I would otherwise repeat each time when I make an AJAX request.

I will put the Request.ts file into a resources/ts/modules directory and use it later on for a POST request.

In the Request class, we use the window.wpReactBirdsGlobals variable to access our plugin's specific data. You may have noticed that the name of this variable matches to what we have defined in the Hook class when we called the wp_localize_script function. That's precisely what the wpReactBirdsGlobals variable will contain.

To tell TypeScript about our global variable, I'm going to create a globals.d.ts file in the resources/ts directory with this content:

interface Window {
    wpReactBirdsGlobals: {
        ajaxUrl: string
        nonce: string
    }
}

Now, TypeScript knows that the window object has the wpReactBirdsGlobals property with some data.

You can explore the Request class to understand how it works under the hood, but I will continue with the article and write the function which fetches the list of birds from the server. For this, I'll open the Birds.tsx file and import this Request module from a resources/ts/modules directory.

This is what the final Birds component looks like:

import React, { useState, useEffect } from 'react'
import './Birds.css'
import Request from '../../../modules/Request'

type Bird = {
    url: string
    title: string
}

const Birds: React.FC = () => {
    const [birds, setBirds] = useState<Bird[]>([])

    useEffect(() => fetchBirds(), [])

    function fetchBirds(): void {
        const params = {
            method: 'get_birds',
            params: [
                { name: 'birds_limit', value: '1' },
            ],
        }

        new Request<Bird[]>(params)
            .send()
            .then(resp => setBirds(resp.data))
            .catch(err => console.error(err))
    }

    return (
        <ul className="Birds-list">
            {birds.map(bird => {
                return (
                    <li key={bird.title} className="Birds-item">
                        <img src={bird.url} alt={bird.title} />
                        <h2>{bird.title}</h2>
                    </li>
                )
            })}
        </ul>
    )
}

export default Birds

When we test it in the browser, we can see that it works just fine:

We see only one bird, that's because we pass a birds_limit parameter with value 1. When we change it to 2, the two birds will show up.

Conclusion

In this article, I showed you my workflow when it comes to developing a WordPress plugin with React and TypeScript. You can extend the code as it is written here to create a more complex plugin.

Of course, it's not everything that I wanted to show you, if you're interested in learning more about web development, you can subscribe for new posts. If you want more WordPress tutorials or any other tutorials, you can let me know on social media, and I'll write them as soon as I get the chance.

Keywords: ts, docker, oop, psr12, ajax, frontend, backend, fullstack