Re-energize Your
Workflow

with React and Redux

Jonathan Kemp

@jonkemp

Jonathan Kemp

Senior Front End Engineer

@ Scripps Networks

Knoxville, TN

The Kemp Family

Follow Along:

Slides - https://slides.com/jonkemp/react-redux/live

Repo - https://github.com/jonkemp/recalculator

Why React?

Why REact?

  • Build user interfaces
  • Reusable UI components
  • Data changes over time
  • Large applications

What Problems Does
React solve?

Problems:

  • Modifying the DOM is expensive
  • Managing state changes in the DOM is complex

Solutions:

  • Performs updates as efficiently as possible
  • Manages UI updates for you

Fast

  • React makes use of a virtual DOM.
  • When data changes, React decides if an update is needed by building a new virtual DOM and comparing (diffing) them.
  • React only updates the parts that changed.

the React ecosystem:

What you should LEarn

In This Order:

  1. React itself
  2. npm
  3. JavaScript "bundlers"
  4. ES6
  5. Routing
  6. Flux

You don't need to learn all of these
to be productive with React.

Learning REact

Misconception:

You need to spend a lot of time setting up tooling to start to learn React.

Reality:

  • No tooling is required.
  • Learn the tooling once you are comfortable with the basics.

Components

Problem

Expressing display logic in template languages can be cumbersome

Components

  • No templates
  • Markup and logic are tied together

JSX

create JavaScript objects
using HTML syntax

* Not Required

Simple

  • JSX provides the readability of HTML
  • "Designers" can contribute React code with JSX
// To generate a link in React using pure JavaScript:

React.createElement('a', {href: 'https://facebook.github.io/react/'}, 'Hello!');


// With JSX this becomes:

<a href="https://facebook.github.io/react/">Hello!</a>

Updates

  • Dynamic data is represented as "props"
  • Passed as attributes in JSX syntax
  • Handles "state"

Best PRactices

  • Most components should be stateless
  • Parent components manage state
  • Children just render the data as props

Demo Time

Building Complex Applications

With React

NPM

  • The most popular way to share JavaScript
  • Most reusable components, libraries and tools in the React ecosystem are installed with npm
  • CommonJS modules (i.e. everything in npm) cannot be used natively in the browser

JavaScript Bundlers

JavaScript Bundlers

JavaScript “bundlers” “bundle” these modules into .js files that you can include in your web page with a <script> tag

Examples

  • Webpack
  • Browserify

Why Webpack?

Webpack

  • Split your app into multiple files
  • Often replaces grunt or gulp
  • Supports AMD, CommonJS, among other module systems (Angular, ES6)
// webpack.config.js
module.exports = {
  entry: [
    'webpack/hot/dev-server',
    'webpack-dev-server/client?http://localhost:8080',
    path.resolve(__dirname, 'app/main.js')
  ],
  output: {
    path: path.resolve(__dirname, 'build'),
    filename: 'bundle.js'
  },
  devtool: 'source-map',
  module: {
    loaders: [
      {
        test: /\.js$/,
        loader: 'babel-loader',
        query: {
          presets: ['es2015', 'react']
        }
      },
      { test: /\.css$/, loader: 'style-loader!css-loader' }
    ]
  }
};

Webpack Config

{
  "name": "recalculator",
  "version": "1.0.0",
  "description": "This is an example app built with React and Redux.",
  "main": "app/main.js",
  "scripts": {
    "build": "webpack",
    "dev": "webpack-dev-server --devtool eval --progress --colors --hot --content-base build",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": "jonkemp/recalculator",
  "author": "Jonathan Kemp <kempdogg@gmail.com> (http://jonkemp.com/)",
  "license": "BSD-2-Clause",
  "dependencies": {
    "bootstrap": "^3.3.6",
    "pubsub-js": "^1.5.3",
    "react": "^15.1.0",
    "react-dom": "^15.1.0",
    "react-redux": "^4.4.5",
    "redux": "^3.5.2"
  },
  "devDependencies": {
    "babel-core": "^6.9.1",
    "babel-loader": "^6.2.0",
    "babel-preset-es2015": "^6.9.0",
    "babel-preset-react": "^6.5.0",
    "css-loader": "^0.23.1",
    "file-loader": "^0.9.0",
    "style-loader": "^0.13.1",
    "url-loader": "^0.5.7",
    "webpack": "^1.13.1",
    "webpack-dev-server": "^1.14.1"
  }
}

ES6

major upgrade to the language

* Not Required

Learning ES6

  • If you just want to get things done with React, skip this
  • Not necessarily the preferred way (docs in ES5)
  • Not supported in browsers yet (your bundler can translate it for you)

Routing

Learning routing

  • Useful for Single-page Applications
  • Most popular router in the React ecosystem is 'react-router'
  • Don’t use a router if you aren’t building a SPA

Flux

Do I need Flux?

  • React is good at managing state.
  • Flux should only be added once many components have already been built.
  • If you aren’t sure if you need it, you don’t need it.

What problems does Flux solve?

  • Managing state in components is an anti pattern
  • Makes maintenance and adding new features easier
  • Makes data changes easy to reason about

the most popular 

AND WELL-DOCUMENTED FLUX LIBRARY
IS REDUX.

REDUX

* Not Required

Problems to Solve

As application complexity grows, managing state becomes harder.

 

At some point, you no longer understand what happens in your app as you have lost control over the when, why, and how of state changes.

WHAT IS REDUX?

  • A state container that will help your applications
    behave consistently
  • Avoids the complexity found in Flux
  • A functional programming approach (to Flux)

Redux Overview

  • State of your app is stored in an object tree inside a single store.
  • Only way to change the state tree is to emit an action, an object describing what happened.
  • To specify how the actions transform the state tree, you write pure reducers.
  • That’s it!
import { createStore } from 'redux';

/**
 * This is a reducer, a pure function with (state, action) => state 
 * signature. It describes how an action transforms the state into 
 * the next state.
 */
function counter(state = 0, action) {
  switch (action.type) {
  case 'INCREMENT':
    return state + 1
  case 'DECREMENT':
    return state - 1
  default:
    return state
  }
}

// Create a Redux store holding the state of your app.
// Its API is { subscribe, dispatch, getState }.
let store = createStore(counter)

// You can use subscribe() to update the UI in response to state 
// changes.
store.subscribe(() =>
  console.log(store.getState())
)

// The only way to mutate the internal state is to dispatch an action.
// The actions can be serialized, logged or stored and later replayed.
store.dispatch({ type: 'INCREMENT' })
// 1
store.dispatch({ type: 'INCREMENT' })
// 2
store.dispatch({ type: 'DECREMENT' })
// 1

The Gist

Benefits

  • Scales well to large and complex apps
  • Enables very powerful developer tools
  • Applications that behave consistently
  • Run in different environments (client, server, and native)
  • Easy to test

Differences from Flux

  • Redux doesn’t have a Dispatcher or support many stores.
  • Instead, there is just a single store with a single root reducing function.
  • Instead of adding stores, you split the root reducer into smaller reducers.

Get Started with Redux

  • Can be used with any view library, independent of React
  • Babel or module bundler not required
  • Install via npm

Three Principles of Redux

Single source of truth

The state of your whole application is stored in an object tree within a single store.

console.log(store.getState())

/* Prints
{
  visibilityFilter: 'SHOW_ALL',
  todos: [
    {
      text: 'Consider using Redux',
      completed: true,
    },
    {
      text: 'Keep all state in a single tree',
      completed: false
    }
  ]
}
*/

Single source of truth

 A single state tree also makes it easier to debug or introspect an application.

State is read-only

The only way to mutate the state is to emit an action, an object describing what happened.

store.dispatch({
  type: 'COMPLETE_TODO',
  index: 1
})

store.dispatch({
  type: 'SET_VISIBILITY_FILTER',
  filter: 'SHOW_COMPLETED'
})

State is read-only

This ensures that neither the views nor the network callbacks will ever write directly to the state.

Changes are made with pure functions

To specify how the state tree is transformed by actions, you write pure reducers.

function visibilityFilter(state = 'SHOW_ALL', action) {
  switch (action.type) {
    case 'SET_VISIBILITY_FILTER':
      return action.filter
    default:
      return state
  }
}

function todos(state = [], action) {
  switch (action.type) {
    case 'ADD_TODO':
      return [
        ...state,
        {
          text: action.text,
          completed: false
        }
      ]
    case 'COMPLETE_TODO':
      return state.map((todo, index) => {
        if (index === action.index) {
          return Object.assign({}, todo, {
            completed: true
          })
        }
        return todo
      })
    default:
      return state
  }
}

import { combineReducers, createStore } from 'redux'
let reducer = combineReducers({ visibilityFilter, todos })
let store = createStore(reducer)

Reducers

  • Pure functions
  • Take the previous state and an action,
    and return the next state
  • Return new state objects, instead of
    mutating the previous state

Recalculator

Features:

  • History tape
  • Every calculation gets saved to the history tape
  • Refer to calculations later or send them right back to the calculator
  • Allows you to see everything you have typed

Actions

Actions:

  • Actions are payloads of information that send data from your application to your store.
  • Actions describe the fact that something happened.
  • They are the only source of information for the store.
  • Send them to the store using store.dispatch().

Action Types

  • Actions must have a type property that indicates the type of action being performed.
  • Types should typically be defined as string constants.

Action Creators

  • Functions that return an action
  • Pass the result to the dispatch() function
  • Can be asynchronous and have side-effects
/*
 * action types
 */

export const ADD_ENTRY = 'ADD_ENTRY';

export const REMOVE_ENTRY = 'REMOVE_ENTRY';

export const UPDATE_RESULT = 'UPDATE_RESULT';

/*
 * action creators
 */

export function addEntry(text) {
  return { type: ADD_ENTRY, text };
}

export function removeEntry(index) {
  return { type: REMOVE_ENTRY, index };
}

export function updateResult(result) {
  return { type: UPDATE_RESULT, result };
}

Reducers

Reducers

Reducers specify how the application’s state changes in response to actions.

Application State

  • Stored as a single object
  • Store data, as well as UI state
// Application State
{
    items:
        [
            {
                id: 1,
                expression: "5 * 3",
                result: 15
            },
            {
                id: 0,
                expression: "5 * 2",
                result: 10
            }
        ],
    lastResult: 15
}

Reducers should:

  • Take the previous state and an action, and return the next state.
  • Given the same arguments, it should calculate the next state and return it.

Reducers should Not:

  • Mutate arguments
  • Perform side effects
import { ADD_ENTRY, REMOVE_ENTRY, UPDATE_RESULT } 
    from '../actions/index';

function calculateApp(state, action) {
  switch (action.type) {

    case ADD_ENTRY:
      return Object.assign({}, state, {
        items: [
          {
            id: state.items.reduce((maxId, todo) => 
                Math.max(todo.id, maxId), -1) + 1,
            expression: action.text,
            result: eval(action.text)
          },
          ...state.items
        ]
      })
  }
}

export default calculateApp;


    case REMOVE_ENTRY:
      return Object.assign({}, state, {
        items: [
          ...state.items.slice(0, action.index),
          ...state.items.slice(action.index + 1)
        ]
      })




    case UPDATE_RESULT:
      return Object.assign({}, state, {
        lastResult: action.result
      })

Object.assign()
  • Copies the values of all enumerable own properties from one or more source objects to a target object
  • Returns the target object
Object Spread Syntax ...
  • Copy enumerable properties from one object to another in a more succinct way
  • Easy to return a new object

Splitting Reducers

  • Split the reducers into separate files
  • Independent, managing different data domains
  • Redux provides a utility called combineReducers()


import { combineReducers } from 'redux'

const calculateApp = combineReducers({
  items,
  lastResult
})

export default calculateApp

Redux Store

Redux Store

Holds the state and takes care of calling your reducer when you dispatch an action

The Store

  • Allows access to state via getState()
  • Allows state to be updated via dispatch(action)
  • Registers listeners via subscribe(listener)
import { createStore, applyMiddleware, compose } from 'redux';
import calculateApp from './reducers/index';

const localStore = store => next => action => {
  const result = next(action);
  localStorage.setItem('recalculator', JSON.stringify(store.getState()));
  return result;
};

const data = JSON.parse(localStorage.getItem('recalculator')) || 
    { items: [], lastResult: 0 },
  store = createStore(
    calculateApp,
    data,
    compose(
      applyMiddleware(localStore)
    )
  );

Data Flow

Data Flow

  1. Dispatch an action
  2. Store calls the reducer
  3. Reducer returns the new state

React Redux

React Bindings for Redux

Presentational and Container Components

Container Components

  • Aware of Redux
  • Subscribe to Redux state
  • Dispatch Redux actions

Presentational Components

  • Not aware of Redux
  • Read data from props
  • Invoke callbacks from props

PRESENTATIONAL COMPONENTS

import React, { PropTypes } from 'react';
import Entry from './Entry';


const EntryList = ({ items, remove }) => (
  <div className="row">
    <ul className="list-group">
      {items.map((item, index) => {
        return <Entry key={item.id} {...item} remove={() => 
            remove(index)} />;
      })}
    </ul>
  </div>
)

export default EntryList;
import React, { PropTypes } from 'react';
import PubSub from 'pubsub-js';

const Entry = React.createClass({
  render() {
    return (
      <li className="list-group-item">
        <div>{this.props.result}</div>
        <div>{this.props.expression}</div>
        <button className="btn btn-warning btn-xs" type="button" 
            onClick={() => {
          PubSub.publish('entry', this.props.result);
        }}>Use Result</button>
        <button className="btn btn-warning btn-xs" type="button" 
            onClick={() => {
          PubSub.publish('entry', this.props.expression);
        }}>Use Expression</button>
        <button className="btn btn-danger btn-xs" type="button" 
            onClick={this.props.remove}>Remove</button>
      </li>
    )
  }
});

export default Entry;
import React, { PropTypes } from 'react';
import PubSub from 'pubsub-js';

const EntryForm = React.createClass({
  componentWillMount() {
    this.pubsub_token = PubSub.subscribe('entry', (topic, value) => {
      this.input.value = value;
      this.handleChange();
    });
  },

  componentWillUnmount() {
    PubSub.unsubscribe(this.pubsub_token);
  },

  handleChange() {
    let value;
    try {
      value = this.input.value !== '' ? eval(this.input.value) : 0;
      this.props.change(value);
    } catch (e) {
       // statements to handle any exceptions
    }
  },

  render() {
    return (
      <div className="row">
        <h3>Recalculator</h3>
        <h1>{this.props.result} <button className="btn btn-success" type="button" 
            onClick={(e) => {
          this.input.value = this.props.result;
        }}><span className="glyphicon glyphicon-arrow-down" aria-hidden="true"></span>
            </button></h1>
        <form className="form-inline" onSubmit={(e) => {
          e.preventDefault();
          this.props.submit(this.input.value);
          this.input.value = '';
        }}>
          <div className="form-group">
            <input className="form-control" type="text" ref={node => {
              this.input = node
            }} onChange={this.handleChange} />
          </div>
          <button className="btn btn-primary" type="submit">Submit</button>
        </form>
      </div>
    );
  }
});

export default EntryForm;

CONTAINER COMPONENTS

React Redux API

React Redux API

  • connect()
  • mapStateToProps()
  • mapDispatchToProps()

generating container components

With the React REdux API

mapStateToProps

Tells how to transform the current Redux store state into the props you want to pass to a presentational component.

import { connect } from 'react-redux';
import { addEntry, updateResult } from '../actions/index';
import EntryForm from '../components/EntryForm';

const mapStateToProps = (state) => {
  return {
    result: state.lastResult
  }
};

const mapDispatchToProps = (dispatch) => {
  return {
    submit: (value) => {
      dispatch(addEntry(value));
    },

    change: (value) => {
      dispatch(updateResult(value));
    }
  }
}

const AddEntry = connect(
  mapStateToProps,
  mapDispatchToProps
)(EntryForm);

export default AddEntry;
mapDispatchToProps

Receives the dispatch() method and returns callback props that you want to inject into the presentational component.

import { connect } from 'react-redux';
import { removeEntry } from '../actions';
import EntryList from '../components/EntryList';

const mapStateToProps = (state) => {
  return {
    items: state.items
  }
};

const mapDispatchToProps = (dispatch) => {
  return {
    remove: (index) => {
      dispatch(removeEntry(index));
    }
  }
}

const PushEntryList = connect(
  mapStateToProps,
  mapDispatchToProps
)(EntryList);

export default PushEntryList;

Summary

React

Performs updates as efficiently as possible.
Manages UI updates for you.

React

Tooling is not required to get started.

Learn the tooling once you are comfortable with the basics.

Redux

Goal is to help your applications behave consistently and make state changes easier to reason about.

For more information:

React - https://facebook.github.io/react/

Redux - http://redux.js.org/

react-webpack-template - https://github.com/petehunt/react-webpack-template

webpack-howto - https://github.com/petehunt/webpack-howto

Resources:

Slides - https://slides.com/jonkemp/react-redux

Repo - https://github.com/jonkemp/recalculator

Thank YOU!

Re-energize Your Workflow with React and Redux

By Jonathan Kemp

Re-energize Your Workflow with React and Redux

  • 282
Loading comments...

More from Jonathan Kemp