Redesigning Redux

Blair Bodnar

Shawn McKay

Part I:  State Management

Part II: Rematch

Part III: Looking Forward

State management

Part I:

You might not need state management.

Have we been overthinking state management?

window.state = {}

{ count: 1 }

State

{ count: 1 }

On Change

Trigger Re-Render

{ count: 1 }

On Change

Get State

Protected State

Send Update

{ count: 1 }

On Change

"INCREMENT_COUNT"

Get

State

Event Sourcing

Send Update

{ count: 1 }

On Change

State Updater

Get

State

Reducing State

Send Update

{ count: 1 }

On Change

State Updater

middleware

Get

State

Pre-Hook

Looks familiar...

onChange callback

pre-hook

protected state

calculate state

send

events

All popular state management libraries are essentially the same.

Redux Vuex Mobx State Tree

Top 3

state state model
getState state getSnapshot
dispatch dispatch dispatch, commit
action creators, reducers mutations actions
subscribe watch, subscribe views
devtools devtools devtools

API

immutable mutate mutate
equality check reactive reactive
1 store* 1 store* multiple stores
Vue only

* generally

Differences

The purpose of any library is to make something more complicated seem simple through abstraction.

timeSaved / timeInvested = apiQuality
timeSaved/timeInvested=apiQualitytimeSaved / timeInvested = apiQuality

Developer Productivity

Writing Less

Reusing More

Smaller Learning Curve

Easier Readability

Extensibility

Redux

+ Ducks

+ Sagas

Dva

Mirror

Reflux

Flux

Fluxxor

+ Thunks

+ Redux Offline

Jumpstate

My State Journey

Rematch

Part II:

Rematch Wraps Redux

Endorsements from Complete
Strangers with Gifs

Init

Store Setup

import { init } from '@rematch/core'


const store = init()

store.getState() // {}

Init Store

import { init } from '@rematch/core'
import { Provider } from 'react-redux'

const store = init()

store.getState() // {}

const App = (
  <Provider store={store}>
    ...
  </Provider>
)

Init Store (with React)

import { init } from '@rematch/core'
import * as models from './models'

const store = init({
  models,
  redux: {
    initialState: {},
    reducers: {},
    middlewares: [],
    enhancers: [],
    rootReducers: {},
    devtoolOptions: {},

    // overwrites
    combineReducers: fn,
    createStore: fn,
  }
})

Redux Options

Models

Organizing Your Code

State

Action Types

Reducers

Model

One place.

Less code.

Same logic.

Action Creators

// models.js

export const modelA = {
  state: 'Hello, Blair!' // initial state
}

Models

// models.js

export const modelA = {
  state: 'Hello, Blair!' // initial state
}

export const modelB = {
  state: 'Hello, Shawn!' // initial state
} 

Models

// models.js

export const modelA = {
  state: 'Hello, Blair!' // initial state
}

export const modelB = {
  state: 'Hello, Shawn!' // initial state
} 
// index.js

import { init } from '@rematch/core'
import * as models from './models'

const store = init({
  models
})

Models

// models.js

export const modelA = {
  state: 'Hello, Blair!' // initial state
}

export const modelB = {
  state: 'Hello, Shawn!' // initial state
} 
// index.js

import { init } from '@rematch/core'
import * as models from './models'

const store = init({
  models
})

store.getState()
//  {
//    modelA: 'Hello, Blair!',
//    modelB: 'Hello, Shawn!'
//  }

Models

export const count = {
  state: 0,
  reducers: {
    up(state) {
      return state + 1
    },
    upBy(state, payload) {
      return state + payload
    }
  },
  effects: {
    async delayedUpBy(payload, rootState) {
      await new Promise(r => setTimeout(r, 1000))
      this.upBy(payload)
    }
  }
}
store.getState()
// { count: 0 }

No more action types

One reducer - One action

// Compared with a normal redux reducer...
const countReducer = (state = 0, action) => {
  switch (action.type) {
    case 'count/up': {
      return state + 1
    }
    case 'count/upBy': {
      return state + action.payload
    }
    default: return state
  }
}

Rematch automatically gives you actions with dispatch built in

import { dispatch } from '@rematch/core'
// dispatch actions from anywhere!
dispatch.count.up()
dispatch.count.upBy(5)

Async actions (API calls)

Calls to dispatch other actions

Functions that talk to the "outside world"

Call them exactly like normal actions

import { dispatch } from '@rematch/core'
// dispatch actions from anywhere!
dispatch.count.up()
dispatch.count.upBy(5)
dispatch.count.delayedUpBy(5)

Count Example

View

No Opinions Here

dispatch.count.upBy(1)

// ===

dispatch({ type: 'count/upBy', payload: 1 })

Dispatch

connect

dispatch({ type: '' })
dispatch.model.action

import React from 'react'
import { connect } from 'react-redux'
import { dispatch } from '@rematch/core'

const Count = props => (
  <div>
    The count is {props.count}
    <button onClick={() => dispatch.count.up()}>up</button>
    <button onClick={() => dispatch.count.upBy(5)}>up by 5</button>
    <button onClick={() => dispatch.count.delayedUpBy(5)}>delayed up by 5</button>
  </div>
)

const mapState = state => ({
  count: state.count
})

const CountContainer = connect(mapState)(Count)

Example: React Redux (Global dispatch)

connect

dispatch({ type: '' })
dispatch.model.action

import React from 'react'
import { connect } from 'react-redux'

const Count = props => (
  <div>
    The count is {props.count}
    <button onClick={props.up}>up</button>
    <button onClick={props.upBy5}>up by 5</button>
    <button onClick={props.delayedUpBy5}>delayed up by 5</button>
  </div>
)

const mapState = state => ({
  count: state.count
})

const mapDispatch = dispatch => ({
  up: () =>  dispatch.count.up()
  upBy5: () => dispatch.count.upBy(5),
  delayedUpBy5: () => dispatch.count.delayedUpBy(5)
})

const CountContainer = connect(mapState, mapDispatch)(Count)

Example: React Redux (mapDispatch)

Plugins

Extending Rematch

dispatch

effects

 

Core Plugins

More Plugins

selectors

subscriptions

persist

react-navigation

etc.

import { init } from '@rematch/core'
import * as models from './models'
import createLoadingPlugin from '@rematch/loading'

const loading = createLoadingPlugin()

const store = init({
  models,
  plugins: [loading]
})

Loading Plugin Setup

import { init } from '@rematch/core'
import * as models from './models'
import createLoadingPlugin from '@rematch/loading'

const myCoolPlugin = {
  config: { ... }, // merges into init config.
  expose: { ... }, // A shared object for plugins to communicate with each other.
  init: (expose) => {
    onModel(model) { // called every time a model is created.
      // do something
    },
    middleware: store => next => action => {
      // do something
      return next(action)
    },
    onStoreCreated(store) { // Run last, after the store is created.
      // do something
    }
  }
}

const store = init({
  models,
  plugins: [myCoolPlugin]
})

Plugin API

Looking forward

Part III:

Is React 16.3 new

Context API

the future of State Management?

State

React Redux

const ThemeContext = React.createContext('light')

class ThemeProvider extends React.Component {
  state = {theme: 'light'}
  render() {
    return (
      <ThemeContext.Provider value={this.state.theme}>
        {this.props.children}
      </ThemeContext.Provider>
    )
  }
}

class App extends React.Component {
  render() {
    return (
      <ThemeProvider>
        <ThemeContext.Consumer>
          {val => <div>{val}</div>}
        </ThemeContext.Consumer>
      </ThemeProvider>
    )
  }
}

Coming in 16.3

Provider/Consumer

View Context

State Library

State Library

View Context

Performance

< Dependencies

Coupled View/Model

Coupled Actions

Shifting Providers

Less Dev Friendly

React only

View Context

Roadmap

1. TypeScript Support

class Logger extends Plugin {
  config = {
    // init config
  }
  onModel(model) {
    // run on each model
  }
  onStoreCreated() {
    // run when store created
  }
  middleware: store => next => action => {
    // add middleware
  }
}

2. Plugins as Classes

3. Multiple Stores

Local Store

dispatch

getState

Global

dispatch => all

getState => all

Blair Bodnar

Shawn McKay

Rematch

Semios

github.com/rematch

hiring!

semios.com/jobs

github.com/blairbodnar

github.com/ShMcK

Redesigning Redux

By Shawn McKay

Redesigning Redux

  • 1,433