Skip to content

Instantly share code, notes, and snippets.

@w33ble
Created May 9, 2017 23:21
Show Gist options
  • Save w33ble/2be4b44c8c590aabf9ec7be90050c7fb to your computer and use it in GitHub Desktop.
Save w33ble/2be4b44c8c590aabf9ec7be90050c7fb to your computer and use it in GitHub Desktop.
state of state, in markdown form

Local state vs app 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 introduce app state?

When we have multiple components that share common state

-- Vuex docs

Single component needs state

  • Component can get its own data, and manage its own state
  • That state is changed in a single place

single component needs state

Multiple leaf components shared state

  • Multiple components share the same data and state
  • Both components may need to update this shared state

shared state across components

Multiple leaf components with detacted shared state

  • The rest of the app doesn't know about the state, or how it's shared
  • Components use some external method to trigger state changes

detatched shared state

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>
    )
  }
}

Redux and 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 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
  • Describe changes in the system as plain objects, state is read-only
    • Actions are dispatched from component, or other actions
  • 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

flux data flow

Initial state example

{
  app: {}, // application stuff...
  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: {
          0: {
            state: 'ready',
            value: {
              type: 'datatable',
              columns: ['id', 'username', 'time', 'cost', 'price'],
            },
            error: null,
          },
          1: {
            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: {
            type: 'expression',
            chain: ['...'],
          }
        }]
      }]
    }
  }
}

Redux complications

Async actions

Deep state objects

Vuex

vuex diagram

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment