React Redux Overview01/15/2017

React and Redux are one of the hottest topics in the JavaScript scene these days. Here is an overview of both these front end libraries and how well they play together.

State

state is a plain javascript object that is used to record and react to user events. Each class based component has it own state object, when the state changes the component re-renders.

Constructor Method

What is super? You call the parent method with super.

Define the state object inside the constructor. use this.setState() to change the state (avoids mutation).

Redux

Redux is a predictable state container for javascript. What does that mean? Here's an explanation using an example:

Imagine an application that contains a list of books. Whenever a user clicks on a book, the app will show details of that book to the user in another container. The app is divided into two parts: the data contained in the app and the view contained in the app. The data is the list of books and the currently selected book. The views are the list view, the list item, and the detail view. All of these parts make up our working, usable app. The contained data will be handled by redux while the views will be handled by react.

We centralize all of our app's data inside a single object - that is what redux is. Let's describe another example to hammer this idea in. Imagine a counter application that keeps track of the current count. Theres an increment count button and a decrement count button, these actions can change the state of the current count. If we separate this app into a data layer and a view layer, what would it look like?

The data contained in the application will be the current count, this would be the redux portion. The views contained in the application will be the current count component and the number changer component. These two containers will work together to make our working, usable app.

One of the most important parts of creating a redux application is figuring out how to design your state. Let's use tinder as an example of how the app's state will be modeled in redux and how react will handle the views of the app.

The general flow of tinder is this: a user is prompted with an image of a user and they can swipe right to like or left to dislike the image. If a person likes a user and that user likes you back, you'll both become matched. When matched, you can initiate a chat with the user that you're matched with.

The data contained in this example is as follows:

  • users (contains images and chat logs)
  • list of users to be reviewed
  • currently viewed user for image swiping (like or dislike)
  • list of current users with a conversation
  • currently viewed conversation

The views contained are as follows:

  • user image card
  • like/dislike buttons (swipe)
  • list of open conversations
  • list of chat messages
  • individual message item

Reducers

What is a reducer? A reducer is a function that returns a piece of the application state. The reducer is only concerned about the value of the state, it has no concern with the keys of the object.

Let's imagine a list of books. Here's how we export our list of books:

export default function() {
  return [
    { title: 'House of Suns', author: 'Alastair Reynolds', pages: 562 },
    { title: 'Chasm City', author: 'Alastair Reynolds', pages: 548 },
    { title: 'Permutation City', author: 'Greg Egan', pages: 359 },
    { title: 'War and Peace', author: 'Leo Tolstoy', pages: 1237 }
  ]
}

Here's how we set up our books as a reducer:

import { combineReducers } from 'redux';
import BooksReducer from './books';

const rootReducer = combineReducers({
  books: BooksReducer
});

export default rootReducer;

Containers

A container is a component that has direct access tot he state produced by redux. We use react-redux to turn components into containers that will work with the application's data (redux). Here's an example of a container component:

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

const styles = {
  author: {
    display: 'block'
  }
}

class BookList extends Component {
  renderList() {
    return this.props.books.map((book) => {
      return (
        <li className="list-group-item" key={book.title}>
          <h4 className="title">
            {book.title}
            <small className="author" style={styles.author}>
              {book.author}
            </small>
          </h4>
        </li>
      )
    })
  }

  render() {
    return (
      <ul className="list-group col-sm-4">
        {this.renderList()}
      </ul>
    );
  }
}

function mapStateToProps(state) {
  // whatever is returned will show up as props
  // inside of BookList
  return {
    books: state.books
  };
}

export default connect(mapStateToProps)(BookList);

Action and Action creators

How do actions work? Imagine a user clicks on a book item. As the user clicks on the item, an action creator creates an action. The action is then automatically sent to all the reducers. The active book property on state is set to the value returned from the active book reducer. Finally, all reducers processes the action and return the new state. When the new state has been assembled, the containers are notified of the changes to the state. On the notification, the containers will render with the new props.

Now let's see how the code for that will look like. First our actions:

import { combineReducers } from 'redux';
import BooksReducer from './books';
import ActiveBook from './active_book';

const rootReducer = combineReducers({
  books: BooksReducer,
  activeBook: ActiveBook
});

export default rootReducer;

Our active book reducer will look like this:

// state argument is not application state
// only the state this reducer is responsible for
export default function(state = null, action) {
  switch(action.type) {
    case 'BOOK_SELECTED':
      return action.payload;
  }

  return state;
}

We will update our book list container to be:

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { selectBook } from '../actions/index';
import { bindActionCreators } from 'redux';

const styles = {
  author: {
    display: 'block'
  }
}

class BookList extends Component {
  renderList() {
    return this.props.books.map((book) => {
      return (
        <li
          className="list-group-item"
          key={book.title}
          onClick={() => this.props.selectBook(book)}>
          <h4 className="title">
            {book.title}
            <small className="author" style={styles.author}>
              {book.author}
            </small>
          </h4>
        </li>
      )
    })
  }

  render() {
    return (
      <ul className="list-group col-sm-4">
        {this.renderList()}
      </ul>
    );
  }
}

function mapStateToProps(state) {
  // whatever is returned will show up as props
  // inside of BookList
  return {
    books: state.books
  };
}

// anything returned from this function
// will end up as props on the BookList container
function mapDispatchToProps(dispatch) {
  // whenever selectBook is called
  // the result should be passed to all of our reducers
  return bindActionCreators({ selectBook: selectBook }, dispatch);
}

// promote BookList from a component to a container
// it needs to know about this new dispatch method, selectBook
// make it available as a prop
export default connect(mapStateToProps, mapDispatchToProps)(BookList);

Our book details container will look like this:

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

const styles = {
  subtitle: {
    display: 'block'
  }
}

class BookDetail extends Component {
  render() {
    if (!this.props.book) {
      return (
        <p>Select a book to get started.</p>
      );
    }

    return (
      <div>
        <h2 className="title">
          <small style={styles.subtitle}>Details for:</small>
          Title: {this.props.book.title}
        </h2>
        <h4 className="author">Author: {this.props.book.author}</h4>
        <p className="pages">Pages: {this.props.book.pages}</p>
      </div>
    );
  }
}

function mapStateToProps(state) {
  return {
    book: state.activeBook
  };
}

export default connect(mapStateToProps)(BookDetail);

Now when we click on a book, it's details will be displayed on the right.

Middleware

To explain middleware, let's build a weather app.

Here's our basic app component:

import React, { Component } from 'react';
import SearchBar from '../containers/search_bar';

export default class App extends Component {
  render() {
    return (
      <main>
        <SearchBar />
      </main>
    );
  }
}

Whenever you call a function that references this, you'll need to bind it inside the constructor. Imagine a search bar that has an onChange method to keep track of the input value's state. You'll need to bind this inside the constructor method like so:

import React, { Component } from 'react';

export default class SearchBar extends Component {
  constructor(props) {
    super(props);

    this.state = { term: '' };

    this.onInputChange = this.onInputChange.bind(this);
  }

  onInputChange(event) {
    this.setState({ term: event.target.value });
  }

  render() {
    return (
      <form className="search-bar-form">
        <div className="input-group">
          <input type="text"
                 className="form-control"
                 placeholder="Get a five day forecast in your favorite cities"
                 value={this.state.term}
                 onChange={this.onInputChange} />
          <span className="input-group-addon">
            <button type="submit" className="btn btn-secondary">
              Search
            </button>
          </span>
        </div>
      </form>
    )
  }
}

What is a middleware in redux? A middleware is a function that takes an action and depending on the action's type and payload or any other number of factors, the middle wear can choose to let the action pass through, manipulate the action, or it can stop it before it reaches any reducer. A middleware is like a gatekeeper that stops actions and inspects it and decides whether it goes through or not the the reducer. Middleware allows us to do some really interesting things when receiving actions.

Let's look at an example. This is your app's index that hooks up middlewares to your reducers and tells react where to render our app:

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import ReduxPromise from 'redux-promise';

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

const createStoreWithMiddleware = applyMiddleware(ReduxPromise)(createStore);

ReactDOM.render(
  <Provider store={createStoreWithMiddleware(reducers)}>
    <App />
  </Provider>
  , document.querySelector('.container'));

Now, let's set up our action:

import axios from 'axios';

// get your api key from openweather.org by creating an account
const API_KEY = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
const ROOT_URL = `http://api.openweathermap.org/data/2.5/forecast?appid=${API_KEY}`;

export const FETCH_WEATHER = 'FETCH_WEATHER';

export function fetchWeather(city) {
  const url = `${ROOT_URL}&q=${city},us`;
  const request = axios.get(url);

  return {
    type: FETCH_WEATHER,
    payload: request
  }
}

And here's our reducer:

import { FETCH_WEATHER } from '../actions/index';

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

  return state;
}

To show our data we need a container to show a list of cities that are searched along with the data:

import React, { Component } from 'react';
import { connect } from 'react-redux';
import Chart from '../components/chart';

class WeatherList extends Component {
  renderWeather(cityData) {
    const name = cityData.city.name;
    const temps = cityData.list.map(weather => weather.main.temp);
    const pressures = cityData.list.map(weather => weather.main.pressure);
    const humidities = cityData.list.map(weather => weather.main.humidity);

    return (
      <tr key={name}>
        <td>{name}</td>
        <td><Chart data={temps} color="orange" units="k" /></td>
        <td><Chart data={pressures} color="green" units="hPa" /></td>
        <td><Chart data={humidities} color="blue" units="%" /></td>
      </tr>
    );
  }

  render() {
    return (
      <table className="table table-hover">
        <thead>
          <tr>
            <th>City</th>
            <th>Temperature (K)</th>
            <th>Pressure (hPa)</th>
            <th>Humidity (%)</th>
          </tr>
        </thead>
        <tbody>
          {this.props.weather.map(this.renderWeather)}
        </tbody>
      </table>
    )
  }
}

function mapStateToProps({ weather }) {
  return { weather }; // { weather } === { weather: weather }
}

export default connect(mapStateToProps)(WeatherList);

We also need a component to show the charts for our weather data:

import _ from 'lodash';
import React from 'react';
import { Sparklines, SparklinesLine, SparklinesReferenceLine } from 'react-sparklines';

function average(data) {
  return _.round(_.sum(data)/data.length);
}

export default (props) => {
  return (
    <div className="chart">
      <Sparklines heigh={120} width={180} data={props.data}>
        <SparklinesLine color={props.color} />
        <SparklinesReferenceLine type="avg" />
      </Sparklines>
      <div>{average(props.data)} {props.units}</div>
    </div>
  );
};

That's it for middleware, using a weather app as an example. We're done with the react redux overview. For a more detailed look at the information presented here, I recommend taking a look at Stephen Grider's Modern 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.