August 23, 2017
Note: This post is not meant to be about the inner workings of React, Redux or React-Router. This is a post detailing my experiences & understanding in creating an app using all 3 libraries, the problems I faced while doing it & how I got around them.
Some background info on me, professionally I am a backend developer who primarily works with Java. I have worked with Apache Wicket for a long time. For those who don’t know Wicket is a web framework for Java with components at its center. So when I wanted to learn a JavaScript frontend library/framework, I chose React as it was similar to Wicket in terms of creating components and composing them to create views while being lightweight enough that it was easy to learn in isolation.
To start learning React, I made Snooplay: A media player for Reddit. The first version of Snooplay was very simple, it just used React, no routing & no state management. You can checkout the code for v1 here.
In order to extend my knowledge to Redux, Middlewares & React Router, I decided to extend Snooplay with the following goals —
Now that we know, what our goals are, lets get started. Here is some information on important packages —
You can checkout the full package.json file here.
We will create the structure of the app by using Facebook’s create-react-app (CRA) tool. After your install CRA, go to your terminal and type the following command —
create-react-app <app_name>
create-react-app is a utility that creates a pre-configured project with Webpack & Babel and other tools. This was made to get over the configuration issues that people usually face while wiring up different tools together.
This process can take some time, once it finishes you can cd
into the
directory and type npm start
This would open your browser and you should be
able to see the React App.
The entry point for the app is index.js
which then renders the <App />
component from app.js
.
Now we need to install our essential libraries i.e. React-Router & Redux.
npm install --save redux react-router
Both React-Router & Redux are very modular libraries. For example, React-Router can also be used in React-Native projects, whereas Redux has no direct connection with React. To make it easy to use these libraries we will install 2 more libs.
react-redux: Helps connect our components with redux, thus making it easy to read & change state from React components.
npm install —save react-router-dom react-redux
This step was very tricky, with many resources on the web. Some worked with Webpack 1 & not with Webpack 2 while some refused to work at all.
npm install --save-dev css-loader extract-text-webpack-plugin node-sass sass-loader
Before we update our webpack configs, we need to *eject *our project. Eject basically means that we will now take care of the webpack & other config instead of just working with what create-react-app gives us. To eject the project just run the following command —
npm run eject
After this process is completed, you will find a config folder which would
contain 2 webpack files, one is _webpack.config.dev.js _& other is
*webpack.config.prod.js. *As you might have guessed, the dev file your
devlopment profile (used when you do npm start
) and the prod file is your
production profile that performs optimizations before creating a deployment
bundle (run npm run build
)
Now in our webpack.config.dev.js
we will configure the
extract-text-webpack-plugin
to handle the scss files.
3.1) Get the extract — text plugin in the file.
const ExtractTextPlugin = require('extract-text-webpack-plugin');
Also declare the CSS file name -
const cssFilename = 'static/css/[name].[contenthash:8].css';
3.2) Configure rules
In the module.exports
object, find the module
property. In the rules
array
you need to find the following section —
// "oneOf" will traverse all following loaders until one will // match the requirements. When no loader matches it will fall // back to the "file" loader at the end of the loader list. oneOf: [
....
]
Here add the config to process scss
files using appropriate loaders.
{
test: /\.scss$/,
use: ExtractTextPlugin.extract(['css-loader', 'sass-loader']) },
Note: Several examples on the internet use the loaders syntax, but that is deprecated.
Also, you should comment out the css
loader that is pre-configured in the
file.
Before going forward here, I would like to mention 2 resources that helped me greatly in understanding Redux & Thunks.
An introduction to the various terms you are going to see moving forward in reference to Redux.
type
property which helps
reducers in deciding if they have to respond to this action or not. Function
that return actions are called action creators.dispatch
& getState
store functions which allow you to further call
actions from inside the function. Example of such action creator —Example of using Redux-Thunk
I would like to outline the process that I used to create my store.
Bind reducer & properties. This is how the state for Snooplay looks
//As you can see, each property is assigned a reducer.
const rootReducer = combineReducers({ currentPost: ReducerCurrentPost,
posts: ReducerFetchPosts,
subs: ReducerFetchSubreddits,
currentSub: ReducerCurrentSubreddit, loaderVisible: ReducerLoadingState, notification: ReducerNotification, lastPostId: ReducerLastPost
});
Provider
component provided by react-redux
package.Now that we have linked our properties with our reducers, I would like to point
out that in our reducer functions, the _state
property refers to the value
of the property only and not the entire state_. For example, here is my
ReducerCurrentPost-
// here the state is the value of ‘currentPost’ property of the app // state and not the entire state. export default function currentPostReducer(state = null, action) {
}
**Actions & Action Creators. **As described earlier the action is simply a JS object with a type field. Any data that has to be sent to the reducer can be sent as the payload property of the action. Here is what the action creator to set the current post look like-
//This is an action creator, returns an action with a type and //payload. export const setCurrentPost = (post) => { return { type: ACTIONCURRENTPOST, payload: post } }
When this action is dispatched (called) like setCurrentPost(post)
redux sends
this action to all reducers. The the current post reducer can respond like-
import { ACTION_CURRENT_POST } from './../Constants';
export default function currentPostReducer(state = null, action) {
switch (action.type) {
//This reducer checks the current action type
//Returns the post (payload)
//This post is updated in the state
case ACTION_CURRENT_POST: {
return action.payload
}
default: {
return state;
}
}
}
Next, we want our components to update when the associated state is changed. So
lets say we have a component which displays the current post. When the reducer
updates the current post in the state, we want this component to re-render to
show the new post. This is where the connect
method from the react-redux
library comes into play. It connects the react components with its associated
state.
Note: In react world, components which work directly with the store are called Container Components and components which work only on the given props (irrespective of where the props are supplied from) are called Presentation Components.
This is what my PostContainer component looks like. It is directly connected to the store —
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { setCurrentPost } from './../../actions/';
class PostContainer extends Component {
render(){
return(<h1>{this.props.post.title}</h1>)
}
}
function mapStateToProps(state){
return {
post: state.currentPost
}
}
function mapDispatchToProps(dispatch){
return {
actions:{
closePlayer: bindActionCreators(setCurrentPost, dispatch)
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)PostContainer);
So whats happening here?
Here we create a Component that has to render some details about the current post of the application.
mapStateToProps
is responsible for taking the entire application state and
then getting out the relevant pieces needed to render the current component.
These pieces are then provided to the components as props
currentPost
property of the application state is accessed using
the standard props mechanism.mapDispatchToProps
is used to call actions from inside the component. In the
above example, I use the setCurrentPost
action to close the player. you can
call the setCurrentPost
action by calling
this.props.actions.closePlayer(null)
.connect
to wire all these together. Now whenever the
currentPost
property is updated in the state, PostContainer
will be updated
to reflect this change.Note: if you are wondering why to use mapDispatchToProps — Read this StackOverflow answer
After this you can setup React-Router. There are many great examples/guides on how to do this. Just note that do not follow a react-router v3 guide if you are working with v4 as v4 is a complete re-write.
React Router setup was a breeze, one thing that bugged me was navigating to
paths programmatically. To do this in v4, I have used the withRouter
Component
from react-router-dom
package. This is what it looks like —
import React, { Component } from 'react';
import { withRouter } from 'react-router-dom'
class SubmitLink extends Component {
handleClick = () => {
if (this.props.onClick()) {
this.props.history.push(this.props.navigateTo());
}
}
render() {
return (
<a onClick={this.handleClick} className="button"> {this.props.label}</a>)
}
}
}
const NavigableSubmitLink = withRouter(SubmitLink);
export default NavigableSubmitLink;
This component first calls the onClick
property, if that function returns
true, then it calls the navigateTo
function which returns the new path that it
has to go to after computation. Using withRouter
gives us access to updated
match
, location
, and history
props.
Ending Notes —
That’s the end of this article, I have tried to explain all the problems I faced and how I overcome them. If you want to more detailed explanations of React, Redux & Redux-Thunk, look at the resources I have linked above.
Written by Anirudh Varma. Loves to code, leading frontend at SpotDraft.
Follow me on Twitter