Hooking up React

It seems the number of frontend frameworks keep growing. Webpack is great at organizing and bundling them. In this article, we're going to hookup React, because it's my personal choice for development.

In other articles, I'll go through Angular, Vue and CanJS. Let's start from a common place.

Where we left off in the Reactive Development section.

git clone https://github.com/lawwantsin/webpack-course.git
cd webpack-course
git checkout hookup
npm install

Then let's add the relevant libraries and make a new file.

npm install react react-dom
touch src/app.js

In main.js let's add that file and the babel-core/register file that we used on the server side to transform imports into requires.

require("babel-runtime/regenerator")
require("babel-register")
require("webpack-hot-middleware/client?reload=true")
require("./main.css")
require("./images/link.jpg")
require("./index.html")
require("./app")

In src/index.html let's add the root div that react will be rendered into:

<div class="profile">
  <img src="./images/link.jpg">
  <h1>Link's Journal</h1>
  <div id="react-root"></div>
</div>

Normally, I don't like using ids, but in this case, it indicates there will be only one root.

In src/app.js let's place the boilerplate to add any React App to the DOM.

import React from "react"
import ReactDOM from "react-dom"

ReactDOM.render(
  <h1>Hello, from React!</h1>,
  document.getElementById("react-root")
)

So lets start our development server and see where we are:

npm run dev

JSX Error

And we have an error. Seems babel needs more power-ups to make JSX work.

JSX Transform

React is an interesting paradigm. In many ways it's just javascript. The layer of syntactic sugar is pretty thin, but JSX is this layer. It's the part that lets us put plan HTML in our javascript starting with just an opening bracket. <

To transpile this HTML like syntax into real Javascript objects is the babel-preset-react preset. Let's add that now. Your .babelrc should look like this:

{
  "presets": [
    [
      "env",
      {
        "targets": {
          "browsers": ["last 2 versions"]
        },
        "debug": false
      },
    ],
    "babel-preset-react"
  ],
  "plugins": ["transform-runtime"]
}

Okay, nodemon is busy reloading, webpack, recompiling and HMR updating your browser, and when we visit, we see.

Hello from React

Babel's Transformation

Babel transpiles JSX into Javascript functions on the fly and I'd like to look at those to give more of an idea of what's actually happening and why React is really just javascript.

In our terminal run:

babel src/app.js

Babel JSX Output

You can see it turns something as simple as <h1> Into a function call. _react2.default.createElement(). This function is what all your React JSX will be transformed into when Babel gets ahold of it. While it's possible to write React using the createElement function. Using JSX, we get the full power of Javascript, with the declarative syntax of HTML and Babel does it's business and gives us proper errors.

If we take the slash off the closing </h1> We will see a proper error, telling us where to fix our HTML in the terminal.

Babel JSX Error

And in the Browser.

Babel JSX Error

A Real Component Example

It's all well and good to place a small amount of JSX on the page and call it a day, but that's not really the full React workflow. React using a Component architecture and there's one important consideration when using components that I'd like to end on.

In terminal, let's create a component file:

touch src/counter.js

counter.js should include the following:

import React from "react"

export default class extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      count: 0
    }
  }

  climb() {
    this.setState({ count: this.state.count + 1 })
  }

  render() {
    return (
      <div onClick={this.climb.bind(this)}>
        <h1>{this.state.count}</h1>
      </div>
    )
  }
}

And app.js should instead render this:

import Counter from "./counter"

ReactDOM.render(<Counter />, document.getElementById("react-root"))

With ReactDOM.render() we place the root component of our app as the first argument, the DOMNode in the second. We're using the plain document.getElementById() to locate react-root

We can click on the number to see it climbs ever higher, but if we then adjust the counter's code in some way, perhaps add a color style to the output. In counter.js:

...
<h1 style={{ color: "green" }}>
  {this.state.count}
</h1>
...

It reloads to green but the counter has returned to it's initial state. Zero. We can do better than that. Let's see how we can use React-hot-loader to maintain the component's internal state.

React Hot Loader

The effort is to maintain the internal state of the component, even while enjoying Hot Module Reloading. For this we need another library. It'll be worth it tho, I promise.

npm install react-hot-loader

Then in app.js let's add.

import React from "react"
import ReactDOM from "react-dom"
import Counter from "./counter"
import { AppContainer } from "react-hot-loader"

function render(Component) {
  ReactDOM.render(
    <AppContainer>
      <Component />
    </AppContainer>,
    document.getElementById("react-root")
  )
}
render(Counter)

if (module.hot) {
  module.hot.accept("./counter.js", () => {
    const NewCounter = require("./counter.js").default
    render(NewCounter)
  })
}

The AppContainer component should wrap the root component for your React App. In this case it's named Counter, but it could be named AppRoot and contain everything else. We'll see that in a future article.

Finally, in .babelrc add a plugin to the babel-transformation and the react-hot-loader/babel in a development only environment.

  "plugins": ["transform-runtime"],
  "env": {
    "development": {
      "plugins": ["react-hot-loader/babel"]
    }
  }

And add this to the top and bottom of counter.js:

import React from "react"
import { hot } from "react-hot-loader"

class Counter extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      count: 1
    }
  }

  render() {
    return (
      <div onClick={this.climb.bind(this)}>
        <h1>Count: {this.state.count}</h1>
      </div>
    )
  }

  climb() {
    this.setState({
      count: this.state.count + 1
    })
  }
}

export default hot(module)(Counter)

Okay, that's the only changes. We should now have stateful component hot reloading. Restart the dev server manually (bc we changed .babelrc) and you should be able to click the counter, increment it, and change the color now.

In Sum

So that's a starting point. Not too hard. Webpack Babel and React are definitely in the same "ecosystem," so they work to keep compatible with these presets.

In this article we hooked React and JSX into our Webpack setup, we then built a component that uses HMR. We also used the react-hot-loader plugin to finish off a Optimal developer setup. Replaceable modules, including css, all while maintaining state within the component itself.