Michael Washburn Jr's Blog

Refactoring JQuery to React

Written by Michael Washburn Jr | Jan 30, 2018 8:00:58 AM

JQuery is quickly becoming a thing of the past, and in it’s place stands React. I mean, even John Resig, author of JQuery, uses React now. So how can you refactor your website to use React instead of JQuery?

In this article I’ll show you step-by-step how to refactor a website using JQuery into React, using real examples.

I’m going to assume we’re starting with a basic website using JQuery. Specifically, I’ll be writing these examples to work with the project here.

Part 1: Webpack and Babel

The first thing we’re going to do to start using React is build our JavaScript using webpack and babel.

Webpack is a highly customizable module bundler. It can be used with other technologies to compile and transform your source code into a finished product.

Babel will be used to compile ES6 JavaScript into ES5 JavaScript. ES6 is just the next-generation of JavaScript. However, it needs to be compiled because it isn’t supported by all browsers yet.

Using babel will allow us to write our React in ES6, which is a lot cleaner than writing React in ES5 JavaScript.

For example, the following is a React component written in ES5 JavaScript.

var createReactClass = require('create-react-class');
var Greeting = createReactClass({
  render: function() {
    return <h1>Hello, {this.props.name}</h1>;
  }
});

Conversely, this is a component written in ES6 JavaScript:

class Greeting extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

Additionally, we’re going to enable the use of JSX. JSX is an extension of JavaScript which allows you to write in an XML-style notation within JavaScript. What this means, is that instead of building HTML using string formatting and concatenation, we can build HTML directly within our JavaScript.

For example, here’s a React component written with JSX:

class Hello extends React.Component {
  render() {
    return <div>Hello {this.props.toWhat}</div>;
  }
}

ReactDOM.render(
  <Hello toWhat="World" />,
  document.getElementById('root')
);

Alternatively, this is what a React component without JSX looks like:

class Hello extends React.Component {
  render() {
    return React.createElement('div', null, `Hello ${this.props.toWhat}`);
  }
}

ReactDOM.render(
  React.createElement(Hello, {toWhat: 'World'}, null),
  document.getElementById('root')
);

As you can see, life’s a lot easier in the React world when using ES6 and JSX. So how do we do it?

Step 1: Initialize your project as an npm package

The first step is to make your website an npm package. Doing this is easy. First, move all the source files to a new src directory.

mkdir src
mv index.html src/
mv main.js src/

After all the source files have been nicely relocated, run npm init to initialize a new node package. Below is an example of the output from running the command, and the inputs provided.

> npm init
name: people-app
version: (1.0.0)
description: A website for viewing people in a table.
entry point: (src/main.js)
test command:
git repository:
keywords:
author: Michael Washburn Jr
license: MIT
About to write to /home/mike/dev/learn-react/part-1-webpack/package.json:

{
 "name": "people-app",
 "version": "1.0.0",
 "description": "A website for viewing people in a table.",
 "main": "src/main.js",
 "dependencies": {},
 "devDependencies": {},
 "scripts": {
 "test": "echo \"Error: no test specified\" && exit 1",
 },
 "author": "Michael Washburn Jr",
 "license": "MIT"
}

Is this ok? (yes)

Step 2: Building with Webpack

Now that the project has been initialized as a node package, we can install Webpack, Babel, and other packages to get our build system working.

npm install --save-dev webpack webpack-dev-server html-webpack-plugin babel-loader babel-core babel-preset-es2015 babel-preset-react

Now, we need to make a Webpack configuration file. Eventually, you’ll probably want to create a development and production version of this configuration file. For now, however, we’ll just use one configuration.

First, create a new file in the root project folder named webpack.config.js.

First, we need to import the libraries we’ll be using to define our Webpack configuration.

// require imports a package to the file. 
// path is used to normalize paths, similar to os.path.join in python
var path = require('path');
// we'll obviously need webpack
var webpack = require('webpack');
// We'll use this to handle the copying of our index.html file
var HtmlWebpackPlugin = require('html-webpack-plugin');

Now, we’re going to need to know the location of our source files, and the location where we want our project to be built to. So go ahead and define some variables to track these directories using the path package.

// define the build and source directory.
var BUILD_DIR = path.join(__dirname, 'dist');
var SRC_DIR = path.join(__dirname, 'src');

Now we have enough to start writing our actual configuration. The meat and potatoes of a Webpack configuration lies in the module exports. These are the values that are made public within a JavaScript file, which other files can import and use. To start, we’ll need to define an entry point to our JavaScript code and an output file.

// these values get made public to anything importing this file
module.exports = {
  // defines the entry point so the bundler knows where to start
  entry: './src/main.js',
  // designates where the JS bundle is saved
  output: { path: BUILD_DIR, filename: 'bundle.js' },
  ...
};

The next thing we’ll need to add to our Webpack config is a way to copy our html file. We can do this with the HtmlWebpackPlugin we imported earlier. This method will also automatically include your bundled JavaScript file in the generated HTML file, so remove the <script src="main.js"></script> line from the index.html file.

To use the HtmlWebpackPlugin, add the following to your configuration:

module.exports = {
  ...
  // handles the copying of the Index.html file to the build dir
  plugins: [
    new HtmlWebpackPlugin({
      hash: true,
      filename: 'index.html',
      template: SRC_DIR + '/index.html'
    }),
  ],
  ...
}

Now we need to configure Babel to compile our ES6 JavaScript and JSX. This can be done by using the babel-loader module. The configuration is fairly simple, and it’s mostly handled by the default babel-loader settings. All you actually need to do is add the following code:

module.exports = {
  ...
  // handles compilation of JSX
  module: {
    loaders: [
      {
        test: /.js?$/,
        loader: 'babel-loader',
        exclude: /node_modules/,
        query: {
          presets: ['es2015', 'react']
        }
      },
    ]
  },
  ...
}
This just takes all .js files not in the node_modules folder, compiles them, and feeds them to the defined preset plugins, "es2015" and "react".

This will compile the JavaScript, JSX, and HTML into the build directory. Now, all we need to do is add a little extra code to get the webpack development server to serve files from our build directory.

module.exports = {
  ...
  // tells the webpack-dev-server to serve content from the build directory
  devServer: {
    contentBase: BUILD_DIR,
  },
  ...
};

At this point, the project should build. Add the following scripts to your package.json file:

{
  ...
  "scripts": {
    ...
    "build": "webpack --config webpack.config.js",
    "start": "webpack-dev-server --progress --colors"
  }
  ...
}

Now if you run npm run build, your project should build successfully. You can also run npm run start to build and start the development server.

Part 2: Create a React Wrapper

At this point, I like to create a React component to wrap around the entire application. I’ve found this to be a good strategy to refactoring a JQuery app, as it let’s you start changing the overall architecture of the application without making drastic changes.

Step 1: Install React

We’ll first need to install react and react-dom. The react package will give us everything we need to write pure React components, and react-dom will give us the tools we need to render those components in the browser.

npm install --save react react-dom

Step 2: Write your Wrapper Component

Create a new file at src/components/App.js. This will be the top-most React component in the application. Import React and define the new component like so:

// import react. equivalent to `var React = require('react');`
import React from 'react';

export default class App extends React.Component {
  //TODO
}

In the example project, we have a lot of JavaScript in the main.js file. We’re going to move that JavaScript into this component.

Cut and Paste the updateTableWithPeopleinsertPersonIntoTable, and fetchPeople functions into the App component we just made.

export default class App extends React.Component {

  // THIS CHANGED: Needed to add this. before function call
  updateTableWithPeople(people) {
    people.forEach(this.insertPersonIntoTable);
  }

  // no changes here
  insertPersonIntoTable(person) {
    $("#people-table-body").append(
      "<tr>" +
        "<td>" + person.name.first + ' ' + person.name.last + '</td>' +
        "<td>" + person.dob + '</td>' +
        "<td>" + person.email + '</td>' +
        "<td>" + person.phone + '</td>' +
        "<td>" + person.cell + '</td>' +
      "</tr>"
    );
  }

  // THIS CHANGED: Needed to add this. before function call and use an
  // arrow function. This lets you keep the same scope as the calling function.
  // In this case, it means "this" still refers to the parent class. 
  fetchPeople() {
    $.get('https://randomuser.me/api/?results=10',
      // use () => { ... } instead of function() { ... }
      // to keep the same scope as the parent function.
      // this lets us still use the "this." keyword
      // within the callback function.
      (response) => {
        this.updateTableWithPeople(response.results);
      }
    );
  }

}

As you can see from the above code, it’s largely the same, but some minor tweaks had to be made. In updateTableWithPeople, I added this. in front of the call to insertPersonIntoTable in order to call the method now that it’s within the App class.

Additionally, in the fetchPeople method, I had to add this. in front of the call to updateTableWithPeople. However, this required switching the old function callback (defined with function () { ... }) to be an arrow function. When you create a function using the full function declaration, the statements within are executed in the scope within the function. With an arrow function, the statements within are executed with the same scope as the statements outside the arrow function.

TLDR; The arrow function allows us to reference this within the callback.

Now, instead of just calling fetchPeople() at the end of the JavaScript file as was done in Part 0, we can call this function in the componentDidMount function. This way, it’s only called after all the HTML has initially been rendered, preventing any errors if the document isn’t ready to be manipulated.

export default class App extends React.Component {
  ...
  componentDidMount(){
    this.fetchPeople();
  }
  ...
}

Lastly, in our render() function we can add all the HTML from the body in our index.html file.

Cut everything inside the <body> tags of the index.html and paste it into the render function like so:

export default class App extends React.Component {
  ...
  render() {
    return (
      <div className="container">
        <h1>Contact Information for Random People</h1>
        {/* the rest of the <body> code goes here. */}
      </div>
    );
  }
}

One small thing to note here is that class attributes that were in the original index.html file have been renamed to className. This is a quirk within JSX to avoid using the ES6 class keyword.

Step 3: Rendering the App Component

First, update the index.html <body> to look like so:

<body>
  <div id="app"/>
</body>

This gives us a location to inject the rendered component into. We’ll just have to make sure the rendering code puts our React component into the div with an ID of “app”.

Now, delete all the code previously in the main.js file. Since we moved all the useful code into the wrapper component, all we need to do in the main.js file is render the component.

This is where react-dom comes in handy.

// import react and the render function which comes from react-dom
import React from 'react';
import { render } from 'react-dom';
// import the App wrapper component we just defined
import App from './components/App';

// call the render function so that <App/> is rendered on the element with the ID "app"
render(
  <App/>,
  document.getElementById('app')
);

If you have multiple pages, you could always render different wrapper components into different HTML elements here. Alternatively, it you could look into using react-router, but I wouldn’t recommend that until the rest of your app is refactored, since it’s a large architecture change.

At this point, your project should build successfully once again. Except now it has a fancy react component wrapping its logic.

Part 3: Refactoring to Pure React

Right now, we’ve still got a lot of JQuery in use. This conflicts with the architecture of React. It works, but it’s not going to be as fast or maintainable as it would in pure React. So now we have to refactor a little bit more.

Step 1: Create a PeopleTable Component

The first thing I want to do here is make a component for rendering the table of people being shown.

Create a new file named src/components/PeopleTable.js. Inside the render function, return all the HTML for the <table> element, which is currently rendered in the App component.

import React from 'react';

export default class PeopleTable extends React.Component {

  render() {
    return (
      <table className="table">...</table>
    );
  }
  
}

Now, where in JQuery we would insert people into our table by building a new <tr> as a string and inserting it into the DOM, we can just create a list of JSX elements and include it in the return value.

export default class PeopleTable extends React.Component {

  render(){
  
    // assume a list of people is being passed to this component via props
    const people = this.props.people;
    
    // build a list of JSX elements for the people to render rows
    const rows = people.map((person, index) => {
      return (
        <tr key={index}>
          <td>{people.name.first} {people.name.last}</td>
          <td>{person.dob}</td>
          <td>{person.email}</td>
          <td>{person.phone}</td>
          <td>{person.cell}</td>
        </tr>
      );// this would be cleaner if this were another component entirely (e.g. <PersonRow person={person}/>)
    });
    
    return (// include the rows we built in the table body using the {...} syntax
      <table>
        ...
        <tbody>{rows}</tbody>
      </table>
    );
  }
}

We now have a fully functioning PeopleTable. It takes one prop: people. If done correctly, this should be able to be used in another component’s render method like so: <PeopleTable people={[p1, p2, p3]}/>.

Step 2: Render the PeopleTable in the App Component

Now that the PeopleTable component is available, we need to refactor the App component to render it instead of doing the work itself.

First, remove the updateTableWithPeople and insertPersonIntoTable methods. Instead of calling fetchPeople and inserting the results into the table with JQuery, we’re going to save the results to our component’s state and pass the state into the PeopleTable.

To do this, first initialize the state in the component’s constructor method.

export default class App extends React.Component {
  
  constructor(props) {
    super(props);
    
    this.state = {
      people: [],
    }; 
  }
  
  ...
}

Changes to the component state trigger a re-render. Thus, once our API request returns results, the render function will fire with the updated state.

Now, move the API request into the componentDidMount method, and change it to use the fetch() method instead of JQuery. We’ll also need to update it to use the setState method to save the results once they’re returned.

export default class App extends React.Component {
  ...
  componentDidMount() {
    fetch('https://randomuser.me/api/?results=10')
      .then(response => response.json())// needs to parse the JSON first
      .then(json => {
        // now save the results to the state
        this.setState({people: json.results});
      });
  }
  ...
}

Finally, we can make the render method return the PeopleTable with the component state.

// don't forget to import the PeopleTable Component
import PeopleTable from './PeopleTable';

export default class App extends React.Component {
  ...
  render() {
    return (
      <div className="container">
        <h1>Contact Information for Random People</h1>
        <PeopleTable people={this.state.people}/>
      </div>
    );
  }
}

Now the App will render, fetch the list of people, save the people to the app state, and re-render.

Congratulations! You’ve just refactored your first JQuery app into pure React!
So what’s next? If you’re interested in learning more about React, try building a web app from the ground up with React.