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
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.
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
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.
And in the Browser.
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.