Advanced React Redux Overview02/15/2017

This is a follow up to a previous post, where we'll look at more advanced examples using React and Redux. Redux can be a little bit challenging to get used to, but I hope this overview can help you get a better understanding of how React and Redux work together.

Higher order components

Higher order components are component wrappers that take care of functionality for a component. When using connect from react redux we actually create a higher order component that wraps our containers inside higher order components to handle the data flow.

We're going to make a quick authentication app to explain higher order components. The flow of our authentication component will be something like this: If a user tries to visit a protected resources route the component will check if the user is logged in or not. If the user is logged in, we allow the user access. If the user is not logged in, we redirect the user back to the home route.

We'll break down our higher level component into two parts - a resources list component and the required auth higher order component. The two components will be combined into a composed component that will check authentication status before rendering.

Here's this example app's code to further explain higher order components. First our app's index that handles the routing and provider store for redux:

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import { Router, Route, browserHistory } from 'react-router';

import App from './components/app';
import Resources from './components/resources';
import reducers from './reducers';

const createStoreWithMiddleware = applyMiddleware()(createStore);

ReactDOM.render(
  <Provider store={createStoreWithMiddleware(reducers)}>
    <Router history={browserHistory}>
      <Route path="/" component={App}>
        <Route path="resources" component={Resources} />
      </Route>
    </Router>
  </Provider>
  , document.querySelector('.container'));

Here's the app component where our entire app is rendered:

import React, { Component } from 'react';
import Header from './header';

export default class App extends Component {
  render() {
    return (
      <main>
        <Header />
        { this.props.children }
      </main>
    );
  }
}

Now let's build our header to handle the navigation:

import React, { Component } from 'react';
import { Link } from 'react-router';

class Header extends Component {
  authButton() {
    return (
      <button
        type="button"
        className="btn btn-default">
        Sign In
      </button>
    )
  }

  render() {
    return (
      <nav className="navbar navbar-light">
        <ul className="nav navbar-nav">
          <li className="nav-item">
            <Link to="/" className="btn btn-default">Home</Link>
          </li>
          <li className="nav-item">
            <Link to="/resources" className="btn btn-default">Resources</Link>
          </li>
          <li className="nav-item">
            {this.authButton()}
          </li>
        </ul>
      </nav>
    )
  }
}

export default Header;

Now let's build out our resources component, this component will be our authenticated component later:

import React from 'react';

export default () => {
  return (
    <div>
      <h4>Super Special Secret Recipe</h4>
      <ul>
        <li>1 cup sugar</li>
        <li>1 cup pepper</li>
        <li>1 cup salt</li>
      </ul>
    </div>
  )
};

Now that our components have been set in place let's create our actions and reducers to handle our mock authentication. First our action:

import { CHANGE_AUTH } from './types';

export function authenticate(isLoggedIn) {
  return {
    type: CHANGE_AUTH,
    payload: isLoggedIn
  };
}

And here is the reducer:

import { CHANGE_AUTH } from '../actions/types';

export default function(state = false, action) {
  switch (action.type) {
    case CHANGE_AUTH:
      return action.payload;
  }

  return state;
}

Let's also add our authentication reducer to the root reducer:

import { combineReducers } from 'redux';
import authenticationReducer from './authentication';

const rootReducer = combineReducers({
  authenticated: authenticationReducer
});

export default rootReducer;

Now that our authentication action and reducer have been set up let's review how our app will work behind the scenes. When our user clicks the sign in button, our authentication action created is triggered. It returns the action payload to our authentication reducer and the reducer will set the authenticated state to true which will tell the header that the user has been authenticated.

Let's update our header component into a container that will handle our authentication action:

import React, { Component } from 'react';
import { Link } from 'react-router';
import { connect } from 'react-redux';
import * as actions from '../actions';

class Header extends Component {
  authButton() {
    if (this.props.authenticated) {
      return (
        <button
          type="button"
          className="btn btn-danger"
          onClick={() => this.props.authenticate(false)}>
          Sign Out
        </button>
      )
    }

    return (
      <button
        type="button"
        className="btn btn-default"
        onClick={() => this.props.authenticate(true)}>
        Sign In
      </button>
    )
  }

  render() {
    return (
      <nav className="navbar navbar-light">
        <ul className="nav navbar-nav">
          <li className="nav-item">
            <Link to="/" className="btn btn-default">Home</Link>
          </li>
          <li className="nav-item">
            <Link to="/resources" className="btn btn-default">Resources</Link>
          </li>
          <li className="nav-item">
            {this.authButton()}
          </li>
        </ul>
      </nav>
    )
  }
}

function mapStateToProps(state) {
  return { authenticated: state.authenticated }
}

export default connect(mapStateToProps, actions)(Header);

Now that this has been taken care of let's build our higher order component that will hide or show our protected resources route. Let's make a higher order component called require_authentication:

import React, { Component } from 'react';

export default function(ComposedComponent) {
  class Authentication extends Component{
    render() {
      return <ComposedComponent {...this.props} />
    }
  }

  return Authentication;
}

We will use this Higher Order Component to wrap our Resources component with. First let's update our router to use the higher order component as a wrapper for resources:

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import { Router, Route, browserHistory } from 'react-router';

import requireAuth from './components/require_authentication';
import App from './components/app';
import Resources from './components/resources';
import reducers from './reducers';

const createStoreWithMiddleware = applyMiddleware()(createStore);

ReactDOM.render(
  <Provider store={createStoreWithMiddleware(reducers)}>
    <Router history={browserHistory}>
      <Route path="/" component={App}>
        <Route path="resources" component={requireAuth(Resources)} />
      </Route>
    </Router>
  </Provider>
  , document.querySelector('.container'));

Let's update our require authentication component to use redux and react router in order to tell whether the user is signed in or not.

import React, { Component } from 'react';
import { connect } from 'react-redux';

export default function(ComposedComponent) {
  class Authentication extends Component{
    static contextTypes = {
      router: React.PropTypes.object
    }

    componentWillMount() {
      if (!this.props.authenticated) {
        this.context.router.push('/');
      }
    }

    componentWillUpdate(nextProps) {
      if (!nextProps.authenticated) {
        this.context.router.push('/');
      }
    }

    render() {
      return <ComposedComponent {...this.props} />
    }
  }

  function mapStateToProps(state) {
    return { authenticated: state.authenticated };
  }

  return connect(mapStateToProps)(Authentication);
}

Using react router context and connect, we show or hide our resources component if the user is authenticated or not.

Middleware

A general redux app cycle goes something like this: We call an action from react, the action creator returns an action. The action is then sent to the middleware. The middleware has an opportunity to log, stop, modify, or not touch an action, it then forwards the action to the reducer and the reducer produces a new state which is then sent back to react.

In this section we will discuss just the middleware portion of this cycle. Together we'll build an app with dummy data, we'll then replace the data with ajax, and finally we'll write our middleware to help fetch the data.

First let's set up our reducer to handle the data:

import { FETCH_USERS } from '../actions/types';

export default function(state = [], action) {
  switch (action.type) {
    case FETCH_USERS:
      return [ ...state, ...action.payload ];
  }

  return state;
}

We import our types from inside the actions dir, we set up our types file simply like so:

export const FETCH_USERS = 'fetch_users';

And we call our reducer inside of our root reducer:

import { combineReducers } from 'redux';
import usersReducer from './users';

const rootReducer = combineReducers({
  users: usersReducer
});

export default rootReducer;

Let's setup our action creator to handle some dummy data to complete the redux side of the app for now:

import { FETCH_USERS } from './types';

export function fetchUsers() {
  return {
    type: FETCH_USERS,
    payload: [
      { name: 'Jane' },
      { name: 'Alex' },
      { name: 'Jim' }
    ]
  }
}

Now let's make out user list container to show the list of users:

import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as actions from '../actions';

class UserList extends Component {
  componentWillMount() {
    this.props.fetchUsers();
  }

  renderUser(user) {
    return (
      <div className="card card-block">
        <h4 className="card-title">{user.name}</h4>
        <p className="card-text">Cheese Factory</p>
        <a className="btn btn-primary">Email</a>
      </div>
    );
  }

  render() {
    return (
      <div className="user-list">
        {this.props.users.map(this.renderUser)}
      </div>
    );
  }
}

function mapStateToProps(state) {
  return { users: state.users }
}

export default connect(mapStateToProps, actions)(UserList);

Let's update our action creator to handle live data to prepare our app to use middleware to handle promises coming from our request.

import axios from 'axios';
import { FETCH_USERS } from './types';

export function fetchUsers() {
  const request = axios.get('https://jsonplaceholder.typicode.com/users');

  return {
    type: FETCH_USERS,
    payload: request
  }
}

Now that we have our data in place we need to setup our middleware to handle the promise coming back from the payload. Let's briefly explain how our middleware will work: An action calls our async middleware where it will determine if the action contains a promise. If there's no promise then the middleware will send it to the next middleware. If the action does contain a promise then the middleware will wait for it to resolve. Once resolved the middleware will create a new action and send it back through the middleware cycle but this time with the actual data and not the promise.

Now here's our middleware file:

export default function({ dispatch }) {
  return next => action => {
    // if action does not have a payload
    // or the payload does not have .then property
    // we dont care about it, send it through
    if (!action.payload || !action.payload.then) {
      return next(action);
    }

    // make sure the action's promise resolves
    action.payload
      .then(function(response) {
        // create a new action with the old type,
        // but replace the promise with the response data
        const newAction = { ...action, payload: response };
        // take the action and send it through the cycle again
        dispatch(newAction);
      });
  };
}

We also need to update our action creator:

import { FETCH_USERS } from '../actions/types';

export default function(state = [], action) {
  switch (action.type) {
    case FETCH_USERS:
      return [ ...state, ...action.payload.data ];
  }

  return state;
}

And our user list container:

import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as actions from '../actions';

class UserList extends Component {
  componentWillMount() {
    this.props.fetchUsers();
  }

  renderUser(user) {
    return (
      <div className="card card-block">
        <h4 className="card-title">{user.name}</h4>
        <p className="card-text">{user.company.name}</p>
        <a className="btn btn-primary" href={user.website}>Website</a>
      </div>
    );
  }

  render() {
    return (
      <div className="user-list">
        {this.props.users.map(this.renderUser)}
      </div>
    );
  }
}

function mapStateToProps(state) {
  return { users: state.users }
}

export default connect(mapStateToProps, actions)(UserList);

This should cover Middleware in React and Redux, so we'll stop here for now. If you found this post helpful, make sure to come back for more React and Redux examples soon. For a more detailed look at the information presented here, I recommend taking a look at Stephen Grider's Advanced React Redux Course.

Have Big Dreams?

We can achieve so much more when we work together! Feel free to follow me on social media so we can exchange memes.