A Redux Primer

or...

Idiomatic Redux in a React Application with Something Other than a Todo App

Who Am I?

  • Front End JavaScript Engineer
  • Background in Philosophy & Theology
  • Lover of Ultimate & Beer
  • @kyleshevlin
  • kyleshevlin.github.io

What we will cover

  • Actions & Action Creators
  • Reducers
  • Provider & connect()
  • App Organization
  • Presentational & Container Components

or

What We Won't Cover

  • Flux, MobX, and other alternatives
  • Thunks, Sagas, and Epics
  • Roll Your Own™

Something other than a Todo App

WoodWorking for Mere Mortals

F(n) = F(n-1) + F(n-2)

1, 1, 2, 3, 5...

How to tell time

1 + 1 + 2 + 3 + 5 = 12

 

12 * 5 = 60

  • Hours are calculated by combining the value of squares that are red and blue
  • Minutes are calculated by combining the value of green and blue squares and multiplying by 5
  • White squares are ignored

What is Redux?

Redux is a predictable state container for JavaScript apps.

Created by Dan Abramov

How does Redux Work?

2 Step Process

  • An action is dispatched
  • A reducer responds to the action, returns the next state

... but what are Actions & Reducers?

  • Actions are plain JavaScript objects
  • A `type` key is required
  • Other key/value pairs are optional

  const action = {
    type: 'MY_ACTION_TYPE',
    payload: 'the optional payload'
  }

Action Creators

  • Functions that return actions
  • Useful as you start to create more complex dispatches, such as thunks

  import * as types from './constants/actionTypes'

  export function toggleAwesomeness () {
    return { type: types.TOGGLE_AWESOMENESS }
  }
  • A reducer is a function that takes two arguments
    • state
    • action
  • It generally uses a `switch` case to handle your action.type
  • Returns the next state

  const initialState = {
    isAwesome: false
  }

  const reducer = (state = initialState, action) => {
    switch (action.type) {
      case 'TOGGLE_AWESOMENESS':
        return Object.assign({}, state, {
          isAwesome: !state.isAwesome
        })

      default:
        return state
    }
  }

One Big Caveat

Redux uses principles of functional programming. Thus, reducers should be pure functions, returning state that is derived solely from its inputs.


  const initialState = {
    collectionOfGoodies: [
      { id: 1, name: 'Cookies' },
      { id: 2, name: 'Pizza' },
      { id: 3, name: 'Beer' }
    ]
  }

  let nextId = 3

  const reducer = (state = initialState, action) => {
    switch (action.type) {
      case 'ADD_GOODIE':
        return Object.assign({}, state, {
          collectionOfGoodies: [
            ...state.collectionOfGoodies,
            {
              id: nextId++,
              name: action.name
            }
          ]
        })

      default:
        return state
    }
  }

This, don't do this. That's bad. :(

Let's look at the app & some code

React Components & Redux

Presentational Components

Also known as "dumb" components, presentational components render based solely on props passed to them, with no internal state or lifecycle methods used.

Container Components

Container components connect to the store and/or use lifecycle methods to update state and/or props to pass down props to presentational components

Renderless Components*

*Not idiomatic, a phrase I coined and a pattern I find useful

Renderless components contain app logic that utilizes lifecycle methods but does not need a UI

Provider & connect()

Provider and connect() are HOCs that come with the 'react-redux' package.

 

The `<Provider> wraps your application and passes the store down to "connected" components.


  import React from 'react'
  import { Provider } from 'react-redux'
  import store from '../store'

  const App = () => (
    <Provider store={store}>
      {/* Rest of my application structure */}
    </Provider>
  )

  export default App

`connect()` allows us to bind state and actions to our component and pass them in as props. Thus, we do not need to import our store and call dispatches on the store, though this is a legitimate way to use Redux.


  export class MyContainer extends Component {
    // ...
  }

  const mapStateToProps = state => ({
    isAwesome: state.isAwesome,
    collectionOfGoodies: state.collectionOfGoodies
  })

  const mapDispatchToProps = {
    toggleAwesomeness
  }

  export default connect(
    mapStateToProps,
    mapDispatchToProps
  )(MyContainer)

App Architecture

Goals

  • Scalable
  • Manageable
  • Testable

Scaling & Manageability

  • Scope actions (to a model, to a component, etc).
  • Import individual action files to `actions/index.js`
  • combineReducers() is your friend
  • actionTypes in a separate file

Testing

  • `import * as ...` is your friend
  • Exporting your `initialState` may make things easier
  • Container components need their class as well as their connected HOC exported

  export class MyContainer extends Component {
    // ...
  }

  export default connect(mapStateToProps)(MyContainer)

Resources

A Redux Primer

By Kyle Shevlin

A Redux Primer

Slides for my talk at Portland JavaScript Admirers in March

  • 644