NodeJS-React-Redux Tutorial - Part 2: React Router v4 + Redux

In part 2 of this tutorial we are going to integrate React Router V4 and Redux state management

1.3 React Router V4

To begin let’s install React Router V4

npm install react-router-dom --save

React router is a routing library which allows our react app to navigate and also stay in sync with the URL.

We will make it so that if the user goes to the root of our application they see the Home component, but if they go to /about they will see an about page.

Before setting up the routing we must add an About component. Create a file named About.js in the components/layouts/ folder containing the following

import React, { Component} from 'react';

class About extends Component {
    render(){
        return (
            <div>All about MadOwlNews.com</div>
        )
    }
}

export default About;

Next, open up app.js in the route of the src folder and alter it to look like the below

import React, { Component } from 'react';
import './App.css';
import { Route, BrowserRouter } from 'react-router-dom';
import Home from './components/layouts/Home';
import About from './components/layouts/About';

 
class App extends Component {
  render() {
    return (
        <BrowserRouter>
            <div>
                <Route exact path="/" component={Home} />
                <Route path="/about" component={About} />
            </div>
        </BrowserRouter>
       );
  }
}

export default App;

You will see we have imported Router and BrowserRouter from React Router, and also our two Home and About components.

If in the browser you navigate to /about you should see the About page and if you go to / you should see the Homepage.

The next step is to add a header and footer to the app so that each route shares a common layout

Within the src/components/layouts folder create a Layout.js that looks like the following

import React, { Component } from 'react';
 
class Layout extends Component {
    render() {
    return (
        <div>
            <div>
                <h1>MadOwlNews.com - Breaking news about Mad Owls</h1>
            </div>
            <div>
                { this.props.children }
            </div>
        </div>
        );
    }
}

export default Layout;

We then need to implement this Layout file as follows in the /src/app.js

...
import Layout from './components/layouts/Layout';

 
class App extends Component {
  render() {
    return (
    <BrowserRouter>
        <Layout>
            <Route exact path="/" component={Home} />
            <Route path="/about" component={About} />
        </Layout>
    </BrowserRouter>
...

As you can see, whatever is nested between the start and ending tag of our Layout component, is rendered within the component using {this.props.children} allowing us to wrap our app layout around the content.

To test this is working navigate to /about and / and you should see the title h1 on both pages

One final thing is to add some basic navigation links into the Layout file using the react router Link component

...
import { Link } from 'react-router-dom';

class Layout extends Component {
    render() {
    return (
        <div>
            <div>
                <h1>MadOwlNews.com - Breaking news about Mad Owls</h1>
            </div>
            <div>
                <ul>
                    <li><Link to={'/'}>Home</Link></li>
                    <li><Link to={'/about'}>About</Link></li>
                </ul>
...

We now have some functioning navigation. To extend this further lets add the ability to click a news Listing item and navigate to a News detail page.

First create a NewsItemDetail component within /components/presentation to display a full news story

import React, { Component} from 'react';
import PropTypes from 'prop-types';

class NewsItemDetail extends Component {
    render(){
        return (
            <div>
                <h2>News story title</h2>
                <p>Body: test mad owl story text </p>
            </div>
        )
    }
}

export default NewsItemDetail

Now we will add in the routing, editing app.js as follows:

...
import NewsItemDetail from './components/presentation/NewsItemDetail';

 
class App extends Component {
  render() {
    return (
        <BrowserRouter>
            <Layout>
                <Route exact path="/" component={Home} />
                <Route path="/about" component={About} />
                <Route path='/news/:id' component={NewsItemDetail}/>    
            </Layout>
        </BrowserRouter>
...		

This will route any request such as ‘/news/22’ to our NewsItemDetail page, passing 22 into the component, accessible via props.match.params.id

Then lets update our NewsItemListing to turn the title into a link to the NewsItemDetail page, using react router’s Link component, passing in props.id

class NewsItemListing extends Component {
    render(){
        return (
            <div>
                <div><Link to={`/news/${this.props.id}`}><b>{this.props.title}</b></Link></div>
                <div>{this.props.teaser}</div>
            </div>

At the moment you will see that the data within our NewsItemDetail is just the sample data, to sort this we will now introduce some application state management through using React Redux

1.4 Redux integration and state management

Before integrating React-redux it is worthwhile quickly explaining Flux.

Flux is an architecture used by Facebook when they are working with React. The components of this architecture are:

Together they work as follows:

The user interacts with the view. This triggers an action. The action goes to the dispatcher which then invokes a corresponding function. This function changes the data in the store. Upon the store altering, the views are notified, these then update themselves and their childs. This is called a unidirectional data flow.

Redux is an evolution of this architecture.

It follows three principles:

  1. Single source of truth - The state for the entire application is in a single store.

  2. State is read only - The only way to change state is to dispatch an action which will result in the state changing.

  3. Changes are made with pure functions - These pure functions transform the state and are called reducers.

In summary - actions are dispatched, reducers take the actions and based on them modify the application state. Views listen to this state change and alter accordingly.

To begin, install redux

npm install redux react-redux redux-thunk --save

Our objective here will be to create the state which hold our news items, make our News component dynamically list news items based on that state, and then have a NewsItemDetail page show the state of that news item.

To begin we will create some actions

Create 2 folders within src, src/actions and also src/constants. Within the constants folder lets create actionTypes.js containing the following:

export default {
  NEWS_RECEIVED: 'NEWS_RECEIVED'
}

then within the actions folder create an actions.js file

import actionTypes from '../constants/actionTypes';

function newsReceived(news){
    return {
        type: actionTypes.NEWS_RECEIVED,
        news: news
    }
}

Next lets create our store and make it accessible to the application

Create a new folder src/store and within there create a store.js file as follows

import { createStore, combineReducers, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import newsReducer from '../reducers/newsReducer';

const store = createStore(
  combineReducers({
    news: newsReducer
  }),
  applyMiddleware(
    thunk
  )
);

export default store;

As you probably noticed we have imported a file that does not exist yet, newsReducer, so lets create a folder /src/reducers/ and within that create our newsReducer.js:

import constants from '../constants/actionTypes'

var initialState = {
  news: []
}

export default (state = initialState, action) => {

  var updated = Object.assign({}, state)

  switch(action.type) {

    case constants.NEWS_RECEIVED:
      updated['news'] = action.news
      return updated

    default:
      return state
    }
}

As you can see we have an initial state, which is an empty array of news items. The reducer then looks at the action which has been dispatched and if it is the NEWS_RECEIVED action, sets the news state to be the passed in news collection, and returns the updated state.

Next we need to integrate the store with our application. To this we need to edit /src/app.js

...
import { Provider } from 'react-redux';
import store from './stores/store';
...
class App extends Component {
  render() {
    return (
        <Provider store={store}>
            <BrowserRouter>
                <Layout>
                    <Route exact path="/" component={Home} />
                    <Route path="/about" component={About} />
                    <Route path='/news/:id' component={NewsItemDetail}/>    
                </Layout>
            </BrowserRouter>
        </Provider>
       );
  }
}

export default App;

So far we have our action constant for the event, we have our action creator and we have our newsReducer which will take the action and alter the state based on it.

Now lets update our News listing so that it is based upon the state.

To do this we need to hookup News.js to Redux, we’ll do this using redux’s connect function.

News.js

import React, { Component} from 'react';
import NewsItemListing from '../presentation/NewsItemListing';
import { connect } from 'react-redux'

class News extends Component {
    render(){

        const newsItems = this.props.news.map( (news, i) => {
            return ( <li key={i}><NewsItemListing data = {news} /></li> );
        });

        return (
            <div>
                <h2>News Items</h2>
                <ul>
                    {newsItems}
                </ul>
            </div>
        )
    }
}

const mapStateToProps = state => {
    return {
        news: state.news.news
    }
}

export default connect(mapStateToProps)(News)

Here we use the connection function, and the function mapStateToProps to tell the app how to transform the state data into props data.

Within our render function we use map to build our list of News Item listings, passing in the news data in the data prop.

Next we’ll alter NewsItemListing so that it uses this new data prop.

...
            <div>
                <div><Link to={`/news/${this.props.data.id}`}><b>{this.props.data.title}</b></Link></div>
                <div>{this.props.data.teaser}</div>
            </div>
        )
    }
}

NewsItemListing.propTypes = {
    data: PropTypes.shape({
        id: PropTypes.string.isRequired,
        title: PropTypes.string.isRequired,
        teaser: PropTypes.string.isRequired
})

};
...

You’ll see that we also altered the validation of the props, using PropTypes.shape to check the structure of our data

When we run this we see..

No news

In order so that it handles there being no news, lets change /src/containers/News.js slightly

import React, { Component} from 'react';
import NewsItemListing from '../presentation/NewsItemListing';
import { connect } from 'react-redux'

class News extends Component {
    render(){

        const newsItems = this.props.news.map( (news, i) => {
            return ( <li key={i}><NewsItemListing data = {news} /></li> );
        });

        return (
            <div>
                <h2>News Items</h2>
                {(this.props.news.length > 0) ? <ul>{newsItems}</ul> : <div>Sorry we have no news</div>}
            
            </div>
        )
    }
}

...

When no news is present it should now show a friendly message

Ok, the next step is to actually get some news. Lets create a new function in /src/actions/actions.js

For now this function is going to simulate us making an API call

...
export function fetchNews(fakeNews){
    return dispatch => {
        dispatch(newsReceived(fakeNews));
    }
}

Then lets dispatch this fetchNews method when our news component mounts

...
import { fetchNews } from '../../actions/actions'

class News extends Component {

    componentDidMount(){

        var fakeNews = [{
            id: '1',
            title: 'Mad owl chases car',
            teaser: 'Mad owl seen tormenting drivers in Morecambe'
        }, {
            id: '2',
            title: 'Owl stowaway',
            teaser: 'Despicable owl impersonates passenger to board flight to Luton'
        }];
        
        this.props.dispatch(fetchNews(fakeNews));
    }
...    

Now the app should be displaying our two fake news items.

Now we’ll work on NewsItemDetail. Lets store some more data, newsItem. This will be the data of the current news item being viewed.

To implement this, first lets add our new action constant

export default {
    NEWS_RECEIVED: 'NEWS_RECEIVED',
    NEWSITEM_RECEIVED: 'NEWSITEM_RECEIVED'
}

Then we’ll edit /src/actions/actions.js to add in a new action creator for receiving a news item, and a function to simulate an API call to getting the news item

import actionTypes from '../constants/actionTypes';

function newsItemReceived(newsItem){
    return {
        type: actionTypes.NEWSITEM_RECEIVED,
        newsItem: newsItem
    }
}

function newsReceived(news){
    return {
        type: actionTypes.NEWS_RECEIVED,
        news: news
    }
}

export function fetchNews(fakeNews){
    return dispatch => {
        dispatch(newsReceived(fakeNews));
    }
}

export function fetchNewsItem(fakeNewsItem){
    return dispatch => {
        dispatch(newsItemReceived(fakeNewsItem));
    }
}

Next, we’ll update the news reducer to handle our new NewsItem actions and update the state

import constants from '../constants/actionTypes'

var initialState = {
    news: [],
    newsItem: {}
}

export default (state = initialState, action) => {

  var updated = Object.assign({}, state)

  switch(action.type) {

    case constants.NEWS_RECEIVED:
      updated['news'] = action.news
      return updated

    case constants.NEWSITEM_RECEIVED:
      updated['newsItem'] = action.newsItem
      return updated

    default:
      return state
    }
}

Before we edit NewsItemDetail, we will add a new container. Within /src/containers/ create a new file NewsArticle.js

This will be the container responsible for displaying our NewsItemDetail component, along with other features we may add further on such as related news stories or comments.

NewsArticle.js

import React, { Component} from 'react';
import NewsItemDetail from '../presentation/NewsItemDetail';
import { connect } from 'react-redux'
import { fetchNewsItem } from '../../actions/actions'

class NewsArticle extends Component {


    componentDidMount(){

        var fakeNewsItem = {
            id: '1',
            title: 'Mad owl chases car',
            teaser: 'Mad owl seen tormenting drivers in Morecambe',
            body: `Morecambe - Tuesday 8th August 2017

            Yesterday evening motorists were left running for their lives as a mad owl began a campaign of terror on rush traffic. 
            Eye Witness, Eric Barnes said "When I heard it Squawk in the sky above me, I thought I was done for"`
        };
        
        this.props.dispatch(fetchNewsItem(fakeNewsItem));
    }

    render(){
        let { newsItem } = this.props; 

        return (
            <div>
                <h2>News Story</h2>
                <ul>
                    { newsItem ? <NewsItemDetail data={newsItem} /> : null}
                </ul>
            </div>
        )
    }
}

const mapStateToProps = state => {
    return {
        newsItem: state.news.newsItem
    }
}

export default connect(mapStateToProps)(NewsArticle)

This works similar to the News.js container, except that it fetches and displays a single news Item

To use this new NewsArticle container we’ll need to alter our routing in /src/App.js

...
import NewsArticle from './components/containers/NewsArticle';

 
class App extends Component {
  render() {
    return (
        <Provider store={store}>
            <BrowserRouter>
                <Layout>
                    <Route exact path="/" component={Home} />
                    <Route path="/about" component={About} />
                    <Route path='/news/:id' component={NewsArticle}/>    
                </Layout>
            </BrowserRouter>
        </Provider>        
...        

Finally we’ll update NewsItemDetail, adding in our props validation and displaying the passed in props data

import React, { Component} from 'react';
import PropTypes from 'prop-types';
import { fetchNewsItem } from '../../actions/actions'

class NewsItemDetail extends Component {

    render(){
        return (
            <div>
                <h2>{this.props.data.title}</h2>
                <p>{this.props.data.body}</p>
            </div>
        )
    }
}

NewsItemDetail.propTypes = {

    data: PropTypes.shape({
        id: PropTypes.string.isRequired,
        title: PropTypes.string.isRequired,
        body: PropTypes.string.isRequired
    })
};

export default NewsItemDetail

Both the NewsItemListing and NewsItemDetail pages are now displaying data based on the store.

To expand this further we need to begin work on the Node server REST API..

[ To Part 3 ] »