React Forms and WordPress REST API

Recently, I built a plugin that actually contains a full React project with react-app. The app is actually a 3-step form with some validations. I thought it was easier to build it in React because of the component structure and the many validation standards. It was quite fun to build and I’d like to give some pointers on how to build this type of application into your WordPress site.

The little tutorial therefore made into 3 steps:

Requirements

  1. A theme where you can add some files and ability to modify functions.php
  2. ACF (advancedcustomfields installed)
  3. A custom post type initialized with this slug: ‘ws_registration’ which holds the data. More info here for adding Custom Post types.
  4. Ability to install / run react
  5. Possibly, WPML. It can also be run without it. But you’ll need to remove ICL_Language_code and other methods.
  6. Load up ACF group fields from here and install this extension plugin for ACF with the import function. Unless you want to go with your own fields ofcourse!
  7. Uses Woocommerce. Mainly to fetch countries relevant for your shop.

This tutorial is made into 3 steps

  1. Make a plugin and load the relevant files

    It’s necessary to load the JS that create-react-app generates into WordPress by the wp_enqueue_script. So we’ll handle that first

  2. Create a form in reactJS

    Using Formik and other small libraries it’s quite easy to build a simple form relatively fast. I am also loading data from an endpoint to have control over the text.

  3. Post the from to your REST endpoint

    Posting to WordPress from React is a little tricky, but once you know, it’s a breeze.

1. Make a plugin and load the relevant files

In order for keeping things separate and easily extensible we are going to ru this all into a plugin. We need a simple basis for adding our react generated files into WordPress. Here it comes.

Create a folder: wholesale-registration and a file inside called wholesale-registration.php in and add the following. You can change this to whatever you like.

<?php
/**
 * @wordpress-plugin
 * Plugin Name:       Wholesale Registration
 */

defined( 'ABSPATH' ) or die( 'Direct script access disallowed.' );


define( 'WHS_WIDGET_PATH', plugin_dir_path( __FILE__ ) . '/wholesale-registration' );
define( 'WHS_ASSET_MANIFEST', WHS_WIDGET_PATH . '/build/asset-manifest.json' );
define( 'WHS_INCLUDES', plugin_dir_path( __FILE__ ) . '/includes' );

require_once( WHS_INCLUDES . '/enqueue.php' );

Then add a folder in your plugin called includes and add an enqueue.php file and add the following:

This contains all the logic for loading the files given that you follow the same standards.

Also add a file to your Template called “wholesale-registration.php” in a new folder called ‘page-templates’. This file needs to contain the this HTML: “<div id=”root”></div>” so that React loads in it.

<?php
defined( 'ABSPATH' ) or die( 'Direct script access disallowed.' );


add_action( 'init', function() {

    add_filter( 'script_loader_tag', function( $tag, $handle ) {
        if ( ! preg_match( '/^whs-/', $handle ) ) { return $tag; }
        return str_replace( ' src', ' async defer src', $tag );
    }, 10, 2 );

    add_action( 'wp_enqueue_scripts', function() {
   
        // TODO make sure this file exists there
        if(is_page_template( 'page-templates/wholesale-registration.php')) :

            global $sitepress;
            // WPML Super power language switcher...
            $sitepress->switch_lang( ICL_LANGUAGE_CODE );

            $asset_manifest = json_decode( file_get_contents( WHS_ASSET_MANIFEST ), true )['files'];

            if ( isset( $asset_manifest[ 'main.css' ] ) ) {
                wp_enqueue_style( 'whs', get_site_url() . $asset_manifest[ 'main.css' ] );
            }

            wp_enqueue_script( 'whs-runtime', get_site_url() . $asset_manifest[ 'runtime-main.js' ], array(), null, true );

            wp_enqueue_script( 'whs-main', get_site_url() . $asset_manifest[ 'main.js' ], array('whs-runtime'), null, true );

            $data = array(
                'nonce' => wp_create_nonce('wp_rest'),
            );
            wp_localize_script('whs-main', 'whs_global', $data);

            foreach ( $asset_manifest as $key => $value ) {
                if ( preg_match( '@static/js/(.*)\.chunk\.js@', $key, $matches ) ) {
                    if ( $matches && is_array( $matches ) && count( $matches ) === 2 ) {
                        $name = "whs-" . preg_replace( '/[^A-Za-z0-9_]/', '-', $matches[1] );
                        wp_enqueue_script( $name, get_site_url() . $value, array( 'whs-main' ), null, true );
                    }
                }

                if ( preg_match( '@static/css/(.*)\.chunk\.css@', $key, $matches ) ) {
                    if ( $matches && is_array( $matches ) && count( $matches ) == 2 ) {
                        $name = "whs-" . preg_replace( '/[^A-Za-z0-9_]/', '-', $matches[1] );
                        wp_enqueue_style( $name, get_site_url() . $value, array( 'whs' ), null );
                    }
                }
            }

        endif;
    });
});

2. Create a form in react app

Let’s create a form in React. I suppose you have an application or you know how to make one. You can pull up my app from here.

3. Post form to REST API

We can add two methods for the REST API. A get and a post, with their callbacks. You can add this is to your plugin file or in your theme’s functions.php

add_action('rest_api_init', function () {
    register_rest_route( 'xyz/v1', 'wholesale_registration',array(
        'methods'  => 'GET',
        'callback' => 'get_wholesale_registration_data'
    ));
    register_rest_route( 'xyz/v1', 'wholesale_register',array(
        'methods'  => 'POST',
        'callback' => 'save_wholesale_registration_data'
    ));
}

Then also you’ll need to add the functions that go with the callback. Here you can find them and add them to the relevant file. Note that it stores the data in a Custom Post Type. You’ll need to init that as well. Also you’ll need this helper function, get_global_option, together with ACF.

function get_global_option($name) {
    $option = get_field($name, 'option');
    return $option;
}

function get_wholesale_registration_data($request) {

    // TODO NONCE
    $lang = ICL_LANGUAGE_CODE;

    global $sitepress;
    // WPML Super power language switcher...
    $sitepress->switch_lang( ICL_LANGUAGE_CODE );


    $taxonomy = 'product_cat';
    $orderby = 'name';
    $show_count = 0; // 1 for yes, 0 for no
    $pad_counts = 0; // 1 for yes, 0 for no
    $hierarchical = 1; // 1 for yes, 0 for no
    $title = '';
    $empty = 0;


    $parent_cats_args = array(
        'taxonomy' => $taxonomy,
        'child_of' => 0,
        'parent' => 0,
        'orderby' => $orderby,
        'show_count' => $show_count,
        'pad_counts' => $pad_counts,
        'title_li' => $title,
        'hide_empty' => $empty

    );

    $parent_cats = get_categories($parent_cats_args);

    foreach($parent_cats as $parent_cat){

        $cats[] = array('name' => $parent_cat->name, 'id' => $parent_cat->term_id);
    }


    $countries_obj = new WC_Countries();
    $countries  = $countries_obj->get_allowed_countries();
    foreach($countries as $key => $value){

        $c[] = array("value" => $key, "label" => $value);

    }


     $jayParsedAry = [
         "start" => [
             "wholesale_image" => get_global_option('wholesale_image'),
             "wholesale_subtitle" => get_global_option('wholesale_subtitle'),
             "wholesale_title" => get_global_option('wholesale_title'),
             "wholesale_description" => get_global_option('wholesale_description')
         ],
         "form" => [
             "h1" => "Daten",
             "firstName" => get_global_option('wholesale_form_first_name'),
             "lastName" => get_global_option('wholesale_form_last_name'),
             "email" => get_global_option('wholesale_form_email'),
             "dialCode" => "+49 ",
             "telephone" => get_global_option('wholesale_form_phone'),
             "businessName" => get_global_option('wholesale_form_business_name'),
             "businessAddress" => get_global_option('wholesale_form_business_address'),
             "postalCode" => get_global_option('wholesale_form_postalcode'),
             "city" => get_global_option('wholesale_form_city'),
             "country" =>get_global_option('wholesale_form_country'),
             "taxNumber" => get_global_option('wholesale_form_tax_number'),
             "vatNumber" => get_global_option('wholesale_form_vat_number'),
             "businessType" => get_global_option('wholesale_form_business_type'),
             "businessRegistration" => get_global_option('wholesale_form_business_registration'),
             "productCategory" => get_global_option('wholesale_form_product_category'),
             "gdpr" => get_global_option('wholesale_form_gdpr'),
             "files" => [
                 "label" => get_global_option('wholesale_file_upload_message'),
                 "uploaded" => [
                     "h1" => get_global_option('wholesale_file_upload_success'),
                     "p" => "Das ist richtig"
                 ]
             ],
             "validation" => [
                 "firstName" => get_global_option('wholesale_required_message'),
                 "lastName" => get_global_option('wholesale_required_message'),
                 "email" => get_global_option('wholesale_required_message'),
                 "dialCode" => get_global_option('wholesale_required_phone_number_message'),
                 "telephone" => [
                     "valid" => get_global_option('wholesale_required_phone_number_message'),
                     "required" => get_global_option('wholesale_required_message')
                 ],
                 "businessName" => get_global_option('wholesale_required_message'),
                 "businessAddress" => get_global_option('wholesale_required_message'),
                 "postalCode" => get_global_option('wholesale_required_message'),
                 "city" => get_global_option('wholesale_required_message'),
                 "taxNumber" => get_global_option('wholesale_required_message'),
                 "vatNumber" => get_global_option('wholesale_required_message'),
                 "businessType" => get_global_option('wholesale_required_message'),
                 "businessRegistration" => get_global_option('wholesale_required_message'),
                 "productCategory" => get_global_option('wholesale_required_message'),
                 "files" => get_global_option('wholesale_required_message'),
                 "productCategories" => get_global_option('wholesale_required_message'),
                 "gdpr" => get_global_option('wholesale_required_message')
             ]
         ],
         "acknowledge" => [
             "title" => get_global_option('before_confirmation_title'),
             "p" => get_global_option('before_confirmation_description'),
             "gdpr" => get_global_option('wholesale_gdpr_info')
         ],
         "finish" => [
             "title" => get_global_option('thanks_title'),
             "description" => get_global_option('thanks_description'),
             "wholesale_email_address" => get_global_option('wholesale_email_address'),
             "wholesale_catalog" => get_global_option('wholesale_catalog'),
             "wholesale_phone" => get_global_option('wholesale_phone'),
             "wholesale_shop" => get_global_option('wholesale_shop')
         ],
         "buttons" => [
             "wholesale_next_button" => get_global_option('wholesale_next_button'),
             "wholesale_back_button" => get_global_option('wholesale_back_button'),
             "wholesale_submit_button" => get_global_option('wholesale_submit_button'),
             "wholesale_register_button" => get_global_option('wholesale_register_button')
         ],
         "locale" => "de",
         "login_message" => get_global_option('login_message'),
         "login_link" =>
             get_global_option('login_link')
         ,
         "help_message" => get_global_option('help_message'),
         "help_link" =>
             get_global_option('help_link')
         ,
         "categories" =>
             $cats,
         "countries" =>
            $c
     ];


   


    $data = $jayParsedAry;

    $response = new WP_REST_Response($data);
    $response->set_status(200);

    return $response;
}


function save_wholesale_registration_data($request) {

    $request['locale'] = 'de';

    //$url = handle_file($request->get_file_params());

    // it's irrelevant in what language this gets stored..

    $cats = $request['productCategories'];
    foreach ($cats as $k => $v) {

        $nCats[] = $v['label'];
    }

    $nCats = implode(", ", $nCats);
    // probably needs implode

    $data = array(
        'firstName' => sanitize_text_field($request['firstName']),
        'lastName' => sanitize_text_field($request['lastName']),
        'email' => sanitize_email($request['email']),
        'telephone' => sanitize_text_field($request['dialCode']['value']) . ' ' . sanitize_text_field($request['telephone']),
        'businessName' => sanitize_text_field($request['businessName']),
        'businessAddress' => sanitize_text_field($request['businessAddress']),
        'postalCode' => sanitize_text_field($request['postalCode']),
        'city' => sanitize_text_field($request['city']),
        'country' => sanitize_text_field($request['country']['label']),
        'taxNumber' => sanitize_text_field($request['taxNumber']),
        'vatNumber' => sanitize_text_field($request['vatNumber']),
        'gdpr' => sanitize_text_field($request['gdpr']),
        'productCategories' => $nCats,
        'businessType' => sanitize_text_field($request['businessType']['label']),
        'businessRegistration' => site_url() . $request['uploaded'][0],
    );


    $args = array(
        'post_title' => $data['firstName'].' '.$data['lastName'],
        'post_excerpt' => '',
        'post_type' => 'ws_registration',
        'post_status' => 'pending',
    );


    $post_id = wp_insert_post($args);

   

    if($post_id):


        foreach($data as $k => $v){

            update_field($k, $v, $post_id);

        }

    foreach($data as $key => $value){
        update_field($key, $value, $post_id);
    }
        update_post_meta($post_id, 'finalized', 'in_progress');

        $response_info = array('message' => 'Registration Completed', 'success' => true);
    else :
        $response_info = array('message' => 'Registration Failed', 'success' => false);
    endif;

    $code = sha1( $post_id . time() );
    $activation_link = add_query_arg( array( 'key' => $code, 'id' => $post_id ), site_url().'/email-confirmation/');
    add_post_meta( $post_id, 'activation_code', $code, true );
    update_post_meta($post_id, 'activation', 'account_unactivated');





    $response = new WP_REST_Response($response_info);
    $response->set_status(200);

    return $response;
}

One comment

  1. […] This is a simple foolproof way to use the REST API above the wp_admin handlers. I prefer it, I always found the WP_ADMIN solution a little limited. Ofcourse, it works as well with Nonce. This is also quite interesting, since in React applications, you’ll need to access the nonce from somewhere. I’ve made an article about that as well as well as a longer tutorial with example code. […]

Leave a Reply