Behind-the-Scenes: Replacing All of WP Migrate DB Pro’s jQuery with React

#

My latest task for WP Migrate DB Pro is creating a new user interface and rewriting all the JavaScript for the plugin and addons. It is no small feat, let me tell you. Our current codebase consists of thousands of lines of relatively unorganized jQuery code. I’ve given a talk about its current state – it’s a big ‘ol ball of spaghetti!

I’ve also written a bit about React in the past, but I wanted to give everyone a sneak peek, mid-dev cycle, at what it takes to rebuild a popular plugin’s JavaScript codebase using React and related tools.

Getting Started

The first step in rebuilding WPMDB’s user interface was essentially throwing everything out — literally. I took the approach of starting from scratch as the current user interface is a mixture of PHP templates, server-rendered code and jQuery soup.

To get up and running quickly I wanted to use Create React App (CRA) as it is a setup I’m familiar with. I did look at other React frameworks like Next.js and Gatsby, but since server-side rendering and a static, page-based approach is not really something we need, I opted to stick with CRA. CRA is great because it removes the need to manually configure Webpack and Babel while giving you an optimized React application right out of the box.

The biggest caveat I’ve run into in the past with CRA is that it’s difficult to get hot module reloading (live reload) working within the WordPress admin interface. After a quick Google, I saw that Human Made has an awesome starter kit for working with CRA and WordPress called react-wp-scripts. It’s essentially a drop-in for CRA’s react-scripts but adds asset paths to the manifest file so WordPress knows what files to enqueue.

And with that, we are running!

State Management

The next big change when writing a React application vs. a jQuery monolith is that in React you (mostly) represent application state as a JavaScript object. React uses what’s called declarative state whereas jQuery uses imperative state. What this essentially means is in jQuery code you create the UI first and define how it needs to be manipulated when it changes. In React, you do the opposite – declare how the application state should change and the HTML is ‘dumb’ and just updates based on what the state tells it to do.

A good overview of this difference can be found in this Stack Overflow article.

React:

<h2 className='panel-title'>
    {props.title} // Managed by state
</h2>

jQuery:

$('.panel-title').html(someVar);

In the simple example above, it doesn’t look too bad. But when you have a complex application like WP Migrate DB Pro with many, many DOM elements and conditional logic, jQuery updates to many different HTML elements at one time can become very disorganized and complex.

jQuery:

if ( 'undefined' !== typeof data.wpmdb_error && 1 === data.wpmdb_error ) {
    $( '.connection-status' ).html( data.body );
    $( '.connection-status' ).addClass( 'notification-message error-notice migration-error' );

    if ( data.body.indexOf( '401 Unauthorized' ) > -1 ) {
        $( '.basic-access-auth-wrapper' ).show();
    }

    if ( !$( '.pull-push-connection-info' ).hasClass( 'temp-disabled' ) && !$( '.connect-button' ).is( ':visible' ) ) {
        $( '.pull-push-connection-info' ).removeAttr( 'readonly' );
        $( '.connect-button' ).show();
    }

    return;
}

if ( $( '.basic-access-auth-wrapper' ).is( ':visible' ) ) {
    connection_info[ 0 ] = connection_info[ 0 ].replace( /\/\/(.*)@/, '//' );
    connection_info[ 0 ] = connection_info[ 0 ].replace( '//', '//' + encodeURIComponent( $.trim( $( '.auth-username' ).val() ) ) + ':' + encodeURIComponent( $.trim( $( '.auth-password' ).val() ) ) + '@' );
    new_connection_info_contents = connection_info[ 0 ] + '\n' + connection_info[ 1 ];
    $( '.pull-push-connection-info' ).val( new_connection_info_contents );
    $( '.basic-access-auth-wrapper' ).hide();
}

React:

// Handle error response
if ( typeof result.wpmdb_error !== 'undefined' && result.wpmdb_error === 1 ) {
    dispatch( setError( true ) );

    const newState = {
        ...connection_state,
        error: result.body
    };

    if ( result.body.indexOf( '401 Unauthorized' ) > -1 ) {
        dispatch( setAuthFormVisible( true ) );
    }

    dispatch( updateConnectionState( newState ) );

    return;
}

Since React replaces our PHP templates and mixes HTML with JavaScript we can do the following to show the basic auth form and error:

        {show_auth_form && (
            <AuthForm 
                authState={auth_form} 
                updateAuthState={props.setAuthForm} 
                connectionState={connection_state} 
                updateConnectionState={props.updateConnectionState} 
            />
        )}

        {error && (
            <NotificationPanel>
                {connection_state.error}
            </NotificationPanel>
        )}

The code blocks above are essentially responsible for the same functionality, but the React version delegates all of the application state logic to Redux (which we’ll talk about later). Application state variables don’t need to be updated when the HTML is updated as it’s handled behind the scenes.

Contrast this to the jQuery version that updates UI state, sets some values and generally confuses the heck out of any developer looking at the code…

My head hurts

Redux

Part and parcel of managing application state in React is usually bringing in a state management library like Redux.

To see why, let’s look at an example. In WP Migrate DB Pro you configure a migration profile before running a migration. The migration profile is created in one part of the application, but the migration progress overlay is a completely separate part of the application. How do these distinct parts reference the same data and state?

In jQuery, you could manage this situation by saving the current profile in a global variable (😱), saving/loading the data from the server or using some wacky data-* attributes. Fortunately, in React, you don’t need to do this and there are standard approaches for managing application-wide state.

One of the ways to manage ‘global’ application state is to use a state container like Redux. Redux is a global ‘store’ that holds the application’s state. Components can then ‘connect’ to the store to get and set required data.

Redux uses some nifty ways to interact with React, making state management a breeze. You normally use an object mapping to link up your component props to Redux state in a mapStateToProps() function:

const mapStateToProps = ( state ) => {
    return {
        notifications: state.notifications,
        profiles: state.profiles,
    }
}

Then in your component, you can use the props as you would any other React prop:

const ProfilesContainer = props => {
    return (
        <div>
            <h3>{__( 'Saved Profiles', 'wp-migrate-db' )}</h3>
            {props.profiles.saved && (
                <ProfilesList
                    profiles={props.profiles.saved}
                    toggleEdit={props.toggleProfileEdit}
                />
            )}

        </div>
    );
};

The last portion of connecting Redux to React is calling the connect() function itself. The first parameter to the connect() function is our state <=> props mapping, the second is a mapping of functions to ‘dispatch’ changes to Redux state. That’s what the toggleProfileEdit() function does.

export default connect(
    mapStateToProps,
    {
        // ...
        toggleProfileEdit,
    }
)( ProfilesContainer );

Redux requires that state be read-only. This means that everytime you want to modify state you have to create a copy of your state tree (normally just a portion of the entire store). You then make your changes to the copy and ‘dispatch’ an action to update the Redux store through what’s called a ‘reducer’.

In a Redux reducer:

//...
switch ( action.type ) {
    case action_types.TOGGLE_PROFILE_EDIT:
        const toggled = toggleItemInArray( draft.toggled, action.payload );
        draft.toggled = toggled;
        return draft;
//...

Which in turn adds or removes an ID from an array in the Redux store.

The toggleProfileEdit action defines only what happens to the Redux state, nothing else. It keeps things clean and separated.

It may look complex, but once you get the hang of it makes a lot of sense.

To be honest, using Redux has been a breeze when compared to the insanity of jQuery DOM manipulation and data attributes. WP Migrate DB Pro is a rather large JavaScript application and managing application state in Redux makes things much easier.

Devtools

Redux also has the amazing Redux Devtools browser extension. It allows you to watch application state change in real time while also giving you the power of time travel in your application.

Where we’re going, we don’t need roads

Seriously though, Redux devtools displays all the actions that are run against your store sequentially so you can see when and where things break.

Drawbacks

One of the main drawbacks of using something like Redux to manage your state is the additional complexity. Redux enforces some rules about how you can manage your state and it requires you to only use JavaScript objects to represent it. While this sounds OK, in practice it can lead to some hoop jumping as soon as you try to do some complex things.

In my case, rebuilding an existing application using Redux is a complex process as I continually come across instances where I hit the limitations of Redux. There are many FAQ items in the Redux docs – which are fantastic – but it’s a very complex tool that does have a learning curve.

TLDR; is that there is a bit of knowledge and know-how required to correctly implement a Redux store and it does fairly dramatically increase the complexity of an application. In the case of WP Migrate DB Pro, I decided that what Redux brings is worth the complexity hit, but time will tell.

Ultimately though, this all just leads me to write sarcastic tweets 😆…

https://twitter.com/petetasker/status/1113447042460864513?s=20

Components

OK, so we’ve seen how React/Redux’s state management is helpful for a complex application that migrates databases and files, but what else is so great about React?!? I’m glad you asked, Pete! 🙄

React generally wants you to write code in components. Components are self-contained units of code that output a chunk of HTML. Components can be anything from a whole screen to a toggle button. Larger components contain a bunch of smaller components.

With modern JavaScript’s import syntax, it’s trivially easy to load in multiple child components into a parent component. In the case of WP Migrate DB Pro, this structure is extremely helpful to logically break out pieces of functionality.

Screen shot of filesystem list of React components

I’ve currently gone a little crazy with my component count, but that’s what code review’s for, right 😉?

You may have also noticed in the earlier code snippets that I’m using the new React Hooks/functional style of writing components. It’s pretty much the same as writing class-based components but using functions instead of ES6 classes.

I initially started using local state in each component but quickly realized that I needed to use a more robust state management system (hence the introduction of Redux). Functional components are a great fit for a ‘display only’ type of component.

The biggest benefit of using hooks in these new components is that it can lead to more succinct code and less boilerplate. Since I didn’t need much local state, functional components were a great fit.

Wrap Up

At the time of writing, I figure we’re a little over halfway into the rewrite of the WPMDB JavaScript. Would I pick React + Redux again? Probably. The best part about using this combo is that resources are easy to come by and once it all ‘clicks’ it’s definitely a powerful stack for a complex application.

That said I wouldn’t use React (and definitely not Redux) for a smaller, not-very-interactive site.

By using React components, our jumbled up jQuery soup has become a set of self-contained pieces that are assembled when required. Coupled with Redux managing state, it’s a much more logical separation of code and is easy to reason about when diving in.

What’s your take on React and Redux? Let us know!

About the Author

Peter Tasker

Peter is a PHP and JavaScript developer from Ottawa, Ontario, Canada. In a previous life he worked for marketing and public relations agencies. Love's WordPress, dislikes FTP.