In this post I will try to make you understand how a javascript react application with redux and react-router works and how they play together. We will be using this boilerplate.
This post can be taken as part 2. The first one concerns setting up and understanding webpack, using the same boilerplate linked above.
React, Redux, React-Router
This isn’t an introductory post to each library. Although you could still follow along and learn a lot.
Consider these resources for an intro:
- React:
https://facebook.github.io/react/docs/tutorial.html
https://facebook.github.io/react/docs/thinking-in-react.html - Redux: The entire docs are amazing (and not long at all)
http://redux.js.org/docs/introduction/index.html - React-Router: The github tutorial is all you need
https://github.com/reactjs/react-router-tutorial
So let’s recap what each of these babies do:
- React: “A javascript library for building user interfaces”. Reacts excels at easing development of your UI once you get in to it. It forces you to think of your app in components and sub-components that react whenever the state changes.
- Redux: “Predictable state container for JavaScript apps”. Redux is a library that helps you manage your app’s state. It derives from various things, one of them the Flux architecture. Redux forces you to use pure functions to calculate the new state whenever an action happens, so :
(oldState, action) => newState - React-Router: A router library for react, it connects nicely to react and redux to help your app support routes.
ES2015 (ES6) with Babel
If you don’t know about ES2015, the new features for javascript brought in 2015, then you might have some trouble understanding parts of the code in the boilerplate.
Even though it’s not fully supported yet by all browsers except chrome, ES6 is already usable thanks to Babel. Babel is a transpiler that transforms ES6, and even ES7 features, to plain old ES5 (the current universally supported version of javascript).
Here’s a good tutorial on the new ES6 features that compares them to ES5
Index.js
Open up that boilerplate I gave you. Let’s jump right in. Head to /app/index.js. This is the entry point to our app.
import 'babel-polyfill'; import React from 'react'; import { render } from 'react-dom'; import { browserHistory } from 'react-router'; import { syncHistoryWithStore } from 'react-router-redux'; import { AppContainer } from 'react-hot-loader'; import configureStore from './store/configureStore'; import Root from './containers/Root'; import './styles/main.scss'; const store = configureStore(); const history = syncHistoryWithStore(browserHistory, store); render( <AppContainer> <Root store={store} history={history} /> </AppContainer>, document.getElementById('root') ); if (module.hot) { module.hot.accept('./containers/Root', () => { const Root = require('./containers/Root').default; render( <AppContainer> <Root store={store} history={history} /> </AppContainer>, document.getElementById('root') ); }); }
There’s a lot of imports there right. You need to know each one in order to understand the app. Lets go through them:
- babel-polyfill is used to enable some ES2015 features which extend existing objects in javacsript like Object.assign. It also lets you use new built-ins like Promises and Generators.
- React is the library we use in order to create components.
- From react-dom we import the function ‘render’. This is the function that renders the root component to the DOM.
- We import ‘browserHistory’ from react-router. This object contains functionality that lets us manipulate routes.
- syncHistoryWithStore is a function from react-router-redux that syncs the browser history with redux’ store. This library helps redux work with react-router. Every route change will equal to an action dispatched to the store.
- ‘AppContainer’ is basically a wrapper that enables us to do hot reloading of react components. This is thanks to react-hot-loader (RHL). Hot reloading means you can push changes to the browser without having to reload it. This makes making changes faster and you get the big benefit of not losing any app state.
- configureStore is a helper function which will help us configure the store, more on this in the next section.
- Root is the root component which is defined in our project under containers. It serves as the entry point for your actual React components.
- We import all the styling of our project just by using import. Webpack puts the styling inside the javascript file, so you can import styles and inject them in components. This makes styling much more modular. In this case though I just import everything in the index.js file.
Once we import everything, we start by configuring the redux store by calling configureStore(); . We will see more on this when we look at the configureStore.js file
We create a const called history which will contain the browser history synced with the store. This is later passed to the router, making it dispatch actions to the store whenever there is a change in the browser history.
In here we also render the root component, in our case, wrapped around the RHL’s component.
Finally, there’s a conditional. If we are in development mode using hot reloading, each time we save something in our code pertaining to the root component or any children, we rerender Root. That way we see changes to the components reflected almost immediately, without browser reload and without changes to the app’s state.
ConfigureStore.js
Remember this line in index.js:
const store = configureStore();
Let’s see what it does. Open up /app/store/configureStore.js
if (process.env.NODE_ENV === 'production') { module.exports = require('./configureStore.prod'); } else { module.exports = require('./configureStore.dev'); }
Uhm, what is this? just a conditional?
This actually looks at what environment you are in. If you are in a production env it will load a different file (or module) than if you are in a ‘dev’ env.
How does javascript know what environment we are in? Well, let’s go back to the previous article on configuring webpack. If you look at the config file for webpack you will notice this:
new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify('development') })
You can set up variables in webpack with this plugin. They will be available anywhere. In the production config for webpack, you will see that the env variable is set to ‘production’.
Ok, let’s look at the development version of configureStore:
import { createStore } from 'redux'; import rootReducer from '../reducers'; import DevTools from '../containers/DevTools'; export default function configureStore(initialState) { const store = createStore( rootReducer, initialState, DevTools.instrument() ); return store; }
Let’s have a look at the imports:
- createStore from redux does just what it says. It creates a store with the reducer you pass it as the first param. Additionally, an optional initial state can be passed as second param and then as third param it can take enhancers.
- rootReducer is our reducer, we will check the code on it later on. For now just remember that a reducer is a pure function that computes the new state by passing it an action and the old state.
- DevTools is a tool that shows us each state change and the action that triggered it. It’s very helpful. Take a look at the gif on the beginning of the article to see it in action. DevTools is an enhancer of the redux store, just like applyMiddleware (which we don’t need in this simple project).
All that this module does is basically create the store with the rootReducer, an optional initial state and the redux devtools as parameters.
Reducers
Now that we know how the store is created, let’s check out the reducer which lives inside of it. open up /app/reducers/index.js
import { routerReducer as routing } from 'react-router-redux'; import { combineReducers } from 'redux'; import * as types from '../actions/types'; const filter = (state = '', action) => { switch (action.type) { case types.FILTER: return action.filter; default: return state; } }; const rootReducer = combineReducers({ filter, routing }); export default rootReducer;
Imports:
- routerReducer is created by react-router-redux. It’s the reducer which handles changes to the routes.
- combineReducers is a utility function from redux. It combines two or more reducers and unites them under the single state tree.
- All actions. We define these ourselves, we will take a look at them later.
In this example I have only provided a small reducer which is the one that handles the actions of filtering the table. If there’s a change in the filter, an action get’s fired. The state of the filter is then computed to be what the user inputs on the filter. The state is later used in the components to show the filtered item list.
Read part two if you want to know how the Root component and it’s children are set up, how to configure our routes and how the actions are defined.
You can also check out this simple to-do app in react/redux that uses async actions, a lot of es6 and a database to store the to-dos.
Leave a Reply