React React-Router Redux — The all in one guide

Nick Meiremans
AB-IT
Published in
9 min readFeb 27, 2018

TLDR; Full Source

It’s been a long time since I’ve been working in react. Last time Flux was the way to go, React-Router worked totally different, create-react-app gave you a small template instead of pure magic, you had to configure Webpack yourself and Redux was the new kid on the block.

Apparently times has changed for the better and this seems like a perfect opportunity to write another blog post.

The goal of this blog to get you up and coding in no time, without any(at least not much) prior knowledge,basic knowledge of NPM and Nodejs can come in handy as I do not go deeper on this subject. if you want more information about this subject, I suggest you to do a google search as there is pretty much to find on this topic.( Or wait for my next blog post! ;-) )

Technology covered in this blog post:
React 15
Redux 5
React-Router 4

Setup

So first things first, if you do not have node and npm installed you should do it now: https://nodejs.org/en/download/

Also if you do not have the react devtools installed in your browser, i suggest you to do it now: Firefox Chrome

After this we need to install the magic tool to generate our react app:

$npm install -G create-react-app

Now we can generate new react apps, cd to your working directory and:

$create-react-app blogpost

create-react-app will now setup your app!

We suggest that you begin by typing:
cd blogpost
npm start

we will listen to the instructions and to this.

$cd blogpost
$npm start

If everything goes well your browser should open and show this:

Let’s open this directory in your favorite IDE and see what’s under the hood.

public: all static files go here, index.html is pretty good commented, won’t go deeper in this.
src: the sources of your React App.

Let’s install some more things!

We want our web page to be able to process nice URLs and load the right components on that URL so we install React-Router for this:

$npm install -s react-router-dom

We also want Redux to save a state between our components:

$npm install -s redux

Redux is not solely made for react, so we want the react binding:

$npm install -s react-redux

We also want our router to be aware of redux

$npm install --s react-router-redux@next

(At the moment of writing the package that works with version 4 of React Router is still in Beta)

Ok All set to go now.

Let’s bring some structure in that src folder.

Make these folders :
-components
-containers

Our views will reside in the Components directory (JSX files).
Our Containers will reside in the Containers directory.

In a MVC approach a container would be the controller, but as containers also can contain views so this is not a pure controller.
We will follow this approach: When a class access data from Redux, it is a Container, when it only presents a view it is a Component.

React

At first we will make a Blog application which only reads data.

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux';
import { reducer } from './Reducer';
import { Provider } from 'react-redux';
import Blog from './components/Blog.jsx'
const initialState = {
posts: [
{ title : 'My first blogpost', text: "Hello this is the first blogpost", }
]
};
const store = createStore(reducer,initialState);ReactDOM.render(
<Provider store={store}>
<Blog />
</Provider>
, document.getElementById('root'));

initialState : We set an initial state including a blogpost
store : Redux saves all his data in a store, instead of making this store manual we let Redux create one for us with our reducer and initialstate. (we will come back on that reducer later)
ReactDOM.render : Renders the views defined
Provider : We tell React which store we want to use.(Although you can only have one store, this is obligated)
Blog: The view which needed to render
document.getElementById(‘root’) : The id of the div in index.html on which react is going to render this application

We will make a Reducer to satisfy Redux, we will come back to it later when we need to add Posts

Reducer.js

export function reducer(store, action) {
switch(action.type) {
}
return store;
}

We need a view “Blog” so let’s make this

components/blog.jsx

import React, { Component } from 'react';class Blog extends Component {
render() {
return (
<div>
<h1>My Personal Blog</h1>
</div>
);
}
}
export default Blog;

Now you can open your browser window and “My personal Blog” should appear.

Now we want to print out the blogpost which we have set in initialState.

As this posts view will access data, this is a container

containers/Post.jsx

import React from 'react';
import { connect } from 'react-redux';
export class AllPosts extends React.Component {
render() {
return (
<ul>
{this.props.posts.map(this.renderPost.bind(this))}
</ul>
);
}
renderPost(postData) {
return (
<li key={postData.title}>
{postData.title}
</li>);
}
}
function mapStateToProperties(state) {
return {
posts: state.posts
};
}
export const AllPostsContainer = connect(mapStateToProperties)(AllPosts);

*note that i only print the title here, we will print out the text later.

now we need to add this container in Blog.jsx

components/Blog.jsx

import React, { Component } from 'react';
import {AllPostsContainer} from '../containers/Post';
class Blog extends Component {
render() {
return (
<div>
<h1>My Personal Blog</h1>
<AllPostsContainer/>
</div> );
}
}
export default Blog;

We add the container to be rendered after the title.
*small note, React components ALWAYS need to be rendered between divs

This is what you should see in the browser now, good!
If this is not what you see, You can checkout the code until now from github (branch basic)

Redux

Now we want to add new posts to our blogging app. This is where Redux comes to play.

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux';
import { reducer } from './Reducer';
import { Provider } from 'react-redux';
import Blog from './components/Blog.jsx'const initialState = {
posts: [
{ title : 'My first blogpost', text: "Hello this is the first blogpost", }
],
newPost: { title: "",text: "" }
};
const store = createStore(reducer,initialState);ReactDOM.render(
<Provider store={store}>
<Blog />
</Provider>
, document.getElementById('root'));

I have added “newPost” which is a placeholder for our new post.

We want to add a from to submit our Title and Text from our blogpost, so we have to make a new view, as this will be talking to Redux, this is a container.

containers/NewPost.jsx

import React from 'react';
import {connect} from 'react-redux';
import PostActions from './../PostActions';export class NewPost extends React.Component {
render() {
return (
<div>
Title :<input type="text" value={this.props.newTitle} onChange={this.props.inputTitleChanged}/>
Text: <input type="text" value={this.props.newText} onChange={this.props.inputTextChanged}/>
<input type="submit"
onClick={e => this.props.newPostSubmitted(this.props.newTitle, this.props.newText)}/>
</div>
);
}
}
function mapStateToProperties(state) {
return {
newText: state.newPost.text,
newTitle: state.newPost.title
};
}
export const actionCreators = {
inputTextChanged: (event) => {
return {type: PostActions.newPostTextChanged, text: event.target.value}
},
inputTitleChanged: (event) => {
return {type: PostActions.newPostTitleChanged, title: event.target.value}
},
newPostSubmitted: (title, text) => {
return {type: PostActions.newPostCreated, title: title, text: text}
}
};
export const NewPostContainer = connect(mapStateToProperties, actionCreators)(NewPost);

PostActions: we will make this later.
render: we will render 2 input boxes 1 for the title and one for the text and a submit button to submit the form. these values are saved in the properties of this view(props) and when the inputboxes change we want our PostAction to run.
mapStateToProperties: We map the right properties of our state to our views properties.
ActionCreators: this are the actions that will be running, we will make these later

Components/Blog.jsx

import React, { Component } from 'react';
import {AllPostsContainer} from '../containers/Post';
import {NewPostContainer} from '../containers/NewPost';
class Blog extends Component {
render() {
return (
<div>
<h1>My Personal Blog</h1>
<AllPostsContainer/>
<NewPostContainer/>
</div>
);
}
}
export default Blog;

We add our new Container to our mainpage “Blog”

PostActions.js

export default {
newPostCreated: Symbol("NEW_POST_CREATED"),
newPostTextChanged: Symbol("NEW_POST_TEXT_CHANGED"),
newPostTitleChanged: Symbol("NEW_POST_TITLE_CHANGED")
};

Because I don’t like working straight with Strings, we map them and use them by their name

Reducer.js

import PostActions from './PostActions';export function reducer(state, action) {
switch (action.type) {
case PostActions.newPostCreated:
return Object.assign({}, state, {
posts: [
...state.posts,
{title: action.title, text: action.text}
]
});
case PostActions.newPostTextChanged:
return Object.assign({}, state, {
newPost: {
...state.newPost,
text: action.text
}
});
case PostActions.newPostTitleChanged:
return Object.assign({}, state, {
newPost: {
...state.newPost,
title: action.title
}
});
default :
return state;
}
}

PostActions.newPostCreated : we add our title and text as a new post in the posts array from the state
PostActions.newPostTextChanged : When we change the text of our new post, we want it to be saved in the “NewPost” variable we made in index.js
PostActions.newPostTitleChanged: The same, but for the title
default : return state: not necessary , I prefer to return the state while developing, in case my Action was not created yet, it won’t crash the app.

This is all, you can now add posts!

Source code

React-Router

As you have seen, we only dispay the Title. We now want somebody to be able to click on the title and see the blogpost.
We will use React-Router to achieve this.

From the documentation:

React Router keeps your UI in sync with the URL. It has a simple API with powerful features like lazy code loading, dynamic route matching, and location transition handling built right in. Make the URL your first thought, not an after-thought.

Seems perfect. We can make a page /post/:title and let it show our blogpost!

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux'
import { reducer } from './Reducer';
import { Provider } from 'react-redux';
import { ConnectedRouter } from 'react-router-redux'
import createHistory from 'history/createBrowserHistory'
import Main from './components/Main.jsx'
const initialState = {
posts: [
{ title : 'My first blogpost', text: "Hello this is the first blogpost", }
],
newPost: { title: "",text: "" }
};
const history = createHistory();
const store = createStore(reducer, initialState);ReactDOM.render(
<Provider store={store}>
<ConnectedRouter history={history}>
<Main />
</ConnectedRouter>
</Provider>
, document.getElementById('root'));

We use the ConnectedRouter from the react-router-redux package.

import Main from ‘./components/Main.jsx’: We will wrap our Routes in Main.jsx

import createHistory from ‘history/createBrowserHistory’
const history = createHistory()

we will make a history based on the browserhistory

<ConnectedRouter history={history}>: We wrap our Router around our Main component, It will automaticly use the Provider Store.

Main.jsx

import { Switch, Route } from 'react-router-dom'
import * as React from "react";
import Blog from './Blog';
import {BlogPostContainer} from './../containers/BlogPost';
const Main = () => (
<main>
<Switch>
<Route exact path='/' component={Blog}/>
<Route path='/post/:title' component={BlogPostContainer}/>
</Switch>
</main>
)
export default Main

We have two router paths one to our Blog Component and one to Our BlogPostContainer

/containers/BlogPost

import React from 'react'import { Link } from 'react-router-dom'
import {connect} from "react-redux";
const BlogPost = (props) => {
const post = findPost(props.match.params.title,props);
if (!post) {
return <div>Sorry, but the post was not found</div>
}
return (
<div>
<h1>{post.title}</h1>
<p>{post.text}</p>
<Link to='/'>Back</Link>
</div>
)
};
function findPost(title,props){
console.log(props.posts);
for (let i=0; i < props.posts.length; i++) {
if ( props.posts[i].title === title) {
return props.posts[i];
}
}
}
function mapStateToProperties(state) {
return {
posts: state.posts
};
}
export const BlogPostContainer = connect(mapStateToProperties)(BlogPost);

findPost : To filter the right post out of the array
<Link to=’/’>Back</Link> : We use Link to link to pages inside a React app, this way we keep the state and don’t reload the page

And ofcourse, the titles needed to become links:

containers/Post.jsx

import React from 'react';
import { connect } from 'react-redux';
import {Link} from "react-router-dom";
export class AllPosts extends React.Component {
render() {
return (
<ul>
{this.props.posts.map(this.renderPost.bind(this))}
</ul>
);
}
renderPost(postData) {
console.log(postData);
return (
<li>
<Link to={'/post/' +postData.title}> {postData.title}</Link>
</li>);
}
}
function mapStateToProperties(state) {
return {
posts: state.posts
};
}
export const AllPostsContainer = connect(mapStateToProperties)(AllPosts);

This is it, download the full source:

Article originally published here :
https://www.ab-it.io/blog/react-react-router-redux-the-all-in-one-guide/

--

--