The State of State

Joe Fleming

Dev @ Elastic

@w33ble

Local vs Global State

Local state is fine

Redux [adds] indirection to decouple “what happened” from “how things change”

- Dan Abramov, You Might Not Need Redux

When to Add App State

When [you] have multiple components that share common state

Local State Only

Shared State in Leaf Nodes

Global State Manager

Local State Counter

import React, { Component } from 'react';

class Counter extends Component {
  state = { value: 0 };

  increment = () => {
    this.setState(prevState => ({
      value: prevState.value + 1
    }));
  };

  decrement = () => {
    this.setState(prevState => ({
      value: prevState.value - 1
    }));
  };
  
  render() {
    return (
      <div>
        {this.state.value}
        <button onClick={this.increment}>+</button>
        <button onClick={this.decrement}>-</button>
      </div>
    )
  }
}

Reducer plus setState

import React, { Component } from 'react';

const counter = (state = { value: 0 }, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return { value: state.value + 1 };
    case 'DECREMENT':
      return { value: state.value - 1 };
    default:
      return state;
  }
}

class Counter extends Component {
  state = counter(undefined, {});
  
  dispatch(action) {
    this.setState(prevState => counter(prevState, action));
  }

  increment = () => {
    this.dispatch({ type: 'INCREMENT' });
  };

  decrement = () => {
    this.dispatch({ type: 'DECREMENT' });
  };
  
  render() {
    return (
      <div>
        {this.state.value}
        <button onClick={this.increment}>+</button>
        <button onClick={this.decrement}>-</button>
      </div>
    )
  }
}

Stateless Component

import React, { Component } from 'react';

const counter = (state = { value: 0 }, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return { value: state.value + 1 };
    case 'DECREMENT':
      return { value: state.value - 1 };
    default:
      return state;
  }
}

const StatelessCounter = ({ value, increment, decrement }) => {
  return (
    <div>
      {value}
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
    </div>
  );
}

class Counter extends Component {
  state = counter(undefined, {});
  
  dispatch(action) {
    this.setState(prevState => counter(prevState, action));
  }

  increment = () => {
    this.dispatch({ type: 'INCREMENT' });
  };

  decrement = () => {
    this.dispatch({ type: 'DECREMENT' });
  };
  
  render() {
    return (
      <StatelessCounter value={this.state.value} increment={this.increment} decrement={decrement} />
    );
  }
}

Redux

Redux is a predictable state container for JavaScript apps.

Redux Principles

Describe application state as plain objects

and arrays, single source of truth

You can use an initial state object to describe the entire application

Redux Principles

Describe changes in the system as plain

objects, state is read-only

Actions are dispatched from component, or other actions

Redux Principles

Describe the logic for handling changes as

pure functions

State is updated using reducers, which accept an accumulation and a value and returns a new accumulation

Redux Principles

Initial State

{
  transient: { // does not survive a refresh
    selectedElement: 'element-d88c-4bbd-9453-db22e949b92e',
    selectedPage: 'page-f3ce-4bb7-86c8-0417606d6592',
    resolvedArgs: {
      'element-d88c-4bbd-9453-db22e949b92e': {
        expressionContexts: {
          state: 'ready',
          value: {
            type: 'datatable',
            columns: ['id', 'username', 'time', 'cost', 'price'],
          },
          error: null,
        },
      },
    },
  },
  persistent: { // survives refresh, be serialized and be saved
    workpad: {
      name: 'Untitled Workpad',
      id: 'workpad-2235-4af1-b781-24e42a453e5b',
      pages: [{
        id: 'page-f3ce-4bb7-86c8-0417606d6592',
        elements: [{
          id: 'element-d88c-4bbd-9453-db22e949b92e',
          expression: 'demodata().sort("time").pointseries(x="time", y=.math("sum(price)")).line()',
          ast: '...',
        }],
      }],
    },
  },
}

Actions Describe Changes


import { ADD_TODO } from '../actionTypes'

// action creator
export addTodo = (todo) => ({
  type: ADD_TODO, 
  payload: todo,
})

Reducers Make New State


import { ADD_TODO } from '../actionTypes'

export default function todoReducer((state, action) => {
  switch(action.type) {
    case ADD_TODO:
      // handle the request action
      return Object.assign({}, state, {
        todos: state.todos.concat(action.payload),
      });
    default:
      return state;
  }
});

Redux Complications

Async === Advanced

Dispatching From Actions

  • Particularly important for async operations
  • Even in sync operations, it's really handy
  • Smaller, isolated actions

Async & Dispatching


import fetch from 'axios';
import { ADD_TODO_SUCCESS, ADD_TODO_ERROR, ADD_TODO_REQUEST } from '../actionTypes';

// action creator
export addTodo = (todo) => (dispatch, getState) => {
    fetch.get('...')
    .then((data) => {
      dispatch({
        type: ADD_TODO_SUCCESS, 
        payload: data,
      });
    })
    .catch((err) => {
      dispatch({
        type: ADD_TODO_ERROR, 
        error: err,
        payload: null,
      });
    });

    return {
      type: ADD_TODO_REQUEST, 
      payload: null,
    };
  }
};

Deep State Trees

  • Reducers should not know the full state tree

Deep State Trees


import thunk from 'redux-thunk';
import combineReducers from 'redux';
import initialState from './initial_state';
import * as reducers from './reducers';

const rootReducer = combineReducers({
  app: reducers.appReducer,
  transient: combineReducers({
    properties: reducers.transientReducer, 
    resolvedArgs: reducers.resolvedArgsReducer,
  }),
  persistent: combineReducers({
    workpad: combineReducers({
      properties: reducers.workpadReducer, 
      pages: combineReducers({
        properties: reducers.pagesReducer, 
        elements: reducers.elementsReducer,
      })
    })
  }),
});

const store = createStore(rootReducer, initialState, applyMiddleware(thunk));

Vuex

Vuex is a state management pattern and library for Vue.js applications

Vuex Principles

Describe application state using a global

singleton that enforces rules for changes

You can use an initial state object to describe the entire application

Vuex Principles

Changes in the system happen by dispatching

actions and committing mutations

State is mutated in the global singleton, using predictable rules

Vuex Principles

A library implementation that takes advantage of Vue's granular reactivity system

Mutations are done on observable objects that trigger updates in components

Vuex Principles

Vuex Store


import Vuex from 'vuex';

const store = new Vuex.Store({
  state: {
    count: 0,
  },
  mutations: {
    increment(state) {
      state.count++;
    },
    decrement(state) {
      state.count--;
    },
  },
});

Vuex Async


import Vuex from 'vuex';
import fetch from 'axios';

const store = new Vuex.Store({
  state: {
    todos: [],
  },
  mutations: {
    pushTodo(state, todo) {
      state.todos.push(todo);
    },
  },
  actions: {
    addTodo({ commit }) {
      return fetch.get('...')
      .then(data => commit('pushTodo', data));
    },
  },
});

Vuex Async + Dispatch


import Vuex from 'vuex';
import fetch from 'axios';

const store = new Vuex.Store({
  state: {
    todos: [],
  },
  mutations: {
    pushTodo(state, todo){
      state.todos.push(todo);
    },
  },
  actions: {
    addTodo({ commit, dispatch }) {
      return fetch.get('...')
      .then(data => {
        dispatch('anotherAction');
        commit('pushTodo', data)
      });
    },
  },
});

Vuex in Vue Component


import Vue from 'vue';

var todoApp = new Vue({
  el: '#todoApp',
  data: {
    working: false,
  }
  computed: {
    todos() {
      return this.$store.todos;
    },
  },
  methods: {
    addTodo: function (todo) {
      this.working = true;
      this.$store.dispatch('addTodo', todo)
      .then(() => { this.working = false; });
    },
  },
});

Vue + Vuex

If I was going to sum up my experiences with Vue in a sentence, I'd probably say something like "it's just so reasonable"

- Sarah Drasner, Intro to Vue.js

Learning Vue, Part 2

vue-router and vuex

Next Month!

June 28th

State of State

By Joe Fleming

State of State

A look at Redux and Vuex

  • 2,080