The Hottest State Management Libraries

aka Bikeshedding Worldwide

@GantLaborde

State Management

Understanding Why

UI is a function of state

With no prescribed standard

React State

Understanding Why

React is better

The bar is raised

But React HAS state management!

setState - be free

setState - be free

Not so fast hippie!!!

Chain of Custody Props

<FirstComponent meantForSomeChild='hi' />
  <SecondComponent {...this.props} />
  <ThirdComponent {...this.props} />
  <FourthComponent {...this.props} />
  <FifthComponent {...this.props} />
  FinalComponent = (props) => <Button>{props.meantForSomeChild}</Button>
  <ThirdComponent someSetting={props.justMe} />

Facebook?

Flux...

And then... Redux

or is it?

State isn't Solved

...and Accepted

We need a place to decide for ourselves

We are Engineers and Scientists

?

Platforms Galore

WEB

Something Simple

React

React Native

Remove Non-State


  import { AddPackingItem } from "packlist-components";


                           // VS


  import { AddPackingItem } from 'packlist-components/native'

Remove Non-State

addItem, setNewItemText, value, clear

allItems

Accessible Play

Rosetta Stone Style

The Basics

  • setState
  • context
  • Redux
  • MobX

The Unknown

  • react-automata
  • unstated
  • many more...

setState

setState

// Top level state
export default class App extends Component {
  state = {
    allItems: ["nachos", "burritos", "hot dog"],
    newItemName: ""
  };
// ...
}

setState

export default class App extends Component {
 // ...
 // Top level functions

  addItem = () => {
    this.setState(state => ({
      allItems: [...state.allItems, state.newItemName],
      newItemName: ""
    }));
  };

// ...
}

setState

//...
        <AddItems
          addItem={this.addItem}
          setNewItemText={this.setNewItemName}
          value={this.state.newItemName}
          clear={this.clear}
        />
        <ListItems allItems={this.state.allItems} />
//...

setState

NO THRILLS!  It's simple right?

It's a pain, too.

React context

React context

// Top level state
export default class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      allItems: ["nachos", "burritos", "hot dog"],
      newItemName: "",
      addItem: this.addItem,
      setNewItemName: this.setNewItemName,
      clear: this.clear
    };
  }
// ...
}

React context

export default class App extends Component {
 // ...
 // Top level functions

  addItem = () => {
    this.setState(state => ({
      allItems: [...state.allItems, state.newItemName],
      newItemName: ""
    }));
  };

// ...
}

React context

//...
        <PackingContext.Provider value={this.state}>
          <AddItems />
          <ListItems />
        </PackingContext.Provider>
//...

React context

<PackingContext.Consumer>
  {({ newItemName, addItem, setNewItemName, clear }) => (
    <AddPackingItem
      addItem={addItem}
      setNewItemText={setNewItemName}
      value={newItemName}
      clear={clear}
    />
  )}
</PackingContext.Consumer>

React context

React context

 

Where does the magic come from?


import { PackingContext } from "Context/packingContext";

React context

export const PackingDefaults = {
  allItems: ["nacho", "burrito", "hotdog"],
  newItemName: ""
};

export const PackingContext = React.createContext({
  ...PackingDefaults
});
export const PackingDefaults = {
  allItems: ["nacho", "burrito", "hotdog"],
  newItemName: ""
};

export const PackingContext = React.createContext({
  ...PackingDefaults
});

What will this look like if we try to consume context outside of a Provider?

export const PackingDefaults = {
  allItems: ["nacho", "burrito", "hotdog"],
  newItemName: ""
};

export const PackingContext = React.createContext({
  ...PackingDefaults
});

What will this look like if we try to consume context outside of a Provider?

Redux Killer?

Short answer... no.

Redux

Redux

Beginning of 3rd party libraries

Redux

No hierarchy of top-level state

  export default class App extends Component {
    render() {
      return (
        <Provider store={store}>
          <div style={styles}>
            <h2>Welcome to Redux</h2>
            <AddItems />
            <ListItems />
          </div>
        </Provider>
      );
    }
  }

How does a component know to update?

Redux

SimpleList

  import { SimpleList } from "packlist-components";
  import { connect } from "react-redux";

  // What props will this component get from Global state?
  const mapStateToProps = state => ({ value: state.items.myItems });

  // HOC lane
  export default connect(mapStateToProps)(SimpleList);

The Higher Order Component trick

Higher Order Function

A higher order function is a function that takes a function as an argument or returns a function.

Higher Order Component

A higher order component is a function that takes a component as an argument AND returns a component.

Redux

SimpleList

  import { SimpleList } from "packlist-components";
  import { connect } from "react-redux";

  // What props will this component get from Global state?
  const mapStateToProps = state => ({ value: state.items.myItems });

  // HOC lane
  export default connect(mapStateToProps)(SimpleList);

We get a state-intelligent component wrapping our original component.

Redux

AddPackingItem

  • Reads State String
  • Reads State Functions
  • Updates State

Redux

class AddItems extends Component {
  render () {
    const { dispatch, newItemName } = this.props
    return (
      <AddPackingItem
        addItem={() => dispatch(ItemActionCreators.addItem())}
        setNewItemText={(e) => 
          dispatch(ItemActionCreators.setNewItemName(e.target.value))
        }
        value={newItemName}
        clear={() => dispatch(ItemActionCreators.clear())}
      />
    )
  }
}

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

export default connect(mapStateToProps)(AddItems)

Redux


import { AddPackingItem } from "packlist-components";
import { ItemActionCreators } from "../Redux/Actions/items";
import { connect } from "react-redux";

const { setNewItemName, addItem, clear } = ItemActionCreators;

const mapDispatchToProps = {
  setNewItemText: e => setNewItemName(e.target.value),
  addItem,
  clear
};

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

export default connect(mapStateToProps, mapDispatchToProps)(AddPackingItem);

Redux


export const ItemsActions = {
  ADD_ITEM: 'ADD_ITEM',
  CLEAR: 'CLEAR',
  SET_NEW_ITEM_NAME: 'SET_NEW_ITEM_NAME'
}

export const ItemActionCreators = {
  addItem: () => ({ type: ItemsActions.ADD_ITEM }),

  clear: () => ({ type: ItemsActions.CLEAR}),

  setNewItemName: (value) => ({
      type: ItemsActions.SET_NEW_ITEM_NAME,
      value
  })
}

Redux

const INITIAL_STATE = { myItems: ['nacho', 'burrito', 'hotdog'], 
  newItemName: ''}

export function reducer (state = INITIAL_STATE, action) {
  switch (action.type) {
    case ItemsActions.ADD_ITEM:
      return { ...state, myItems: [...state.myItems, state.newItemName],
        newItemName: ''
      }
    case ItemsActions.CLEAR:
      return { ...state, myItems: [] }
    case ItemsActions.SET_NEW_ITEM_NAME:
      return { ...state, newItemName: action.value }
    default:
      return state
  }
}

Redux

import { createStore, applyMiddleware } from 'redux'
import RootReducer from '../Reducers'

let middleware = []

const baseStore = createStore(RootReducer, 
  applyMiddleware(...middleware))

export default initialState => {
  return baseStore
}

Redux Review

  • Scalable
  • Popular
  • Functional
  • Flexible
  • Time travel

MobX

UNLIMITED POWER!!!

MobX

export default class App extends Component {
  render() {
    return (
      <div style={styles}>
        <h2>Welcome to MobX</h2>
        <AddItems />
        <ListItems />
      </div>
    );
  }
}

MobX

import React, { Component } from "react";
import { SimpleList } from "packlist-components";

import ListStore from "../Mobx/listStore";
import { observer } from "mobx-react";

@observer
export default class ListItems extends Component {
  render() {
    return <SimpleList value={[...ListStore.allItems]} />;
  }
}

MobX

@observer
export default class AddItems extends Component {
  render() {
    return (
      <AddPackingItem
        addItem={ListStore.addItem}
        setNewItemText={ListStore.setNewItemName}
        value={ListStore.newItemName}
        clear={ListStore.clear}
      />
    );
  }
}

MobX

What is @observer?

MobX

Q: What is @observer?

A: It's just a HOC wrap

MobX

class ObservableListStore {
  @observable allItems = ["nacho", "burrito", "hotdog"];
  @observable newItemName = "";

  addItem = () => {
    this.allItems.push(this.newItemName);
    this.newItemName = "";
  };
  clear = () => {
    this.allItems = [];
  };
  setNewItemName = e => {
    this.newItemName = e.target.value;
  };
}

Cool ❄️

  • Small code
  • Efficient
  • Easy
  • Mutable

Uncool 🔥

  • Decorator Magic
  • Middleware-ish
  • No prescription
  • No state-tree

MobX

That's the classics

 

Time to go for crazy stuff.

The good suggestions and the bad suggestions!

That's the classics

 

Now for some lesser known libs

Every State Lib

unstated

unstated

  import { Provider } from "unstated";

  export default class App extends Component {
    render() {
      return (
        <Provider>
          <div style={styles}>
            <h2>Welcome to unstated</h2>
            <AddItems />
            <ListItems />
          </div>
        </Provider>
      );
    }
  }

unstated

import React, { Component } from "react";
import ListContainer from "../Unstated/listContainer";
import { Subscribe } from "unstated";
import { SimpleList } from "packlist-components";

export default class ListItems extends Component {
  render() {
    return (
      <Subscribe to={[ListContainer]}>
        {list => <SimpleList value={list.state.allItems} />}
      </Subscribe>
    );
  }
}

unstated

      <Subscribe to={[ListContainer]}>
        {list => (
          <AddPackingItem
            addItem={list.addItem}
            setNewItemText={list.setNewItemName}
            value={list.state.newItemName}
            clear={list.clear}
          />
        )}
      </Subscribe>

unstated

import { Container } from "unstated";

export default class ListContainer extends Container {
  state = {
    allItems: ["nachos", "burritos", "hot dog"],
    newItemName: ""
  };
  addItem = () => {
    this.setState(state => ({ allItems: [...state.allItems, state.newItemName],
    newItemName: "" }));
  };
  setNewItemName = event => {
    this.setState({ newItemName: event.target.value });
  };
  clear = () => {
    this.setState({ allItems: [] });
  };
}

That's just React!?

unstated is what context wishes it will be when it grows up

State so simple, it goes without saying.

👍 Scales like context

👎 Obviously missing the bells and whistles of a more advanced system.

react-automata

Not really a state management solution

But a state manager

Based on xstate

Finite State Machines

import { Machine } from 'xstate';
const lightMachine = Machine({
  key: 'light',
  initial: 'green',
  states: {
    green: {
      on: {
        TIMER: 'yellow',
      }
    },
    yellow: {
      on: {
        TIMER: 'red',
      }
    },
    red: {
      on: {
        TIMER: 'green',
      }
    }
  }
});

const currentState = 'green';
const nextState = lightMachine
  .transition(currentState, 'TIMER')
  .value;

Finite State Machines:

  • Can't get in an unplanned state
  • Are graphable
  • Are iteratively testable
  • Sequence

xstate machine config

export default {
  initial: "idle",
  states: {
    idle: {
      on: {
        CLEAR: {
          idle: {
            actions: ["clear"],
            cond: isTrue
          }
        },
        SET_NEW_ITEM_NAME: {
          loaded: {
            actions: ["setNewItemName"]
          }
        }
      }
    },
    loaded: {
      on: {
        ADD_ITEM: {
          idle: {
            actions: ["addItem"]
          }
        },
        CLEAR: {
          loaded: {
            actions: ["clear"],
            cond: isTrue
          },
        },
        SET_NEW_ITEM_NAME: {
          loaded: {
            actions: ["setNewItemName"],
            cond: isNotEmpty
          },
          idle: {
            actions: ["setNewItemName"],
            cond: isEmpty
          }
        }
      }
    }
  }
};

can't add blank

State of xstate


<State is="idle">Current State 'idle'</State>
<State is="loaded">Current State 'loaded'</State>

Combos of state in

react-automata

import { testStateMachine } from 'react-automata'
  import { App } from '../App'

  test('all state snapshots', () => {
    testStateMachine(App, { fixtures })
  })

Also, ANALYTICS!

Using FSMs

  • Means looking ahead

  • Means lots of automation benefits

  • Means lots of analytic benefits

  • Means visual way to identify late changes

  • Means integrating with other state managers

GraphQL

GraphQL with Apollo

Managing state remote?

Managing state local?

Apollo

apollo-link-state

redux

  • dispatch action
  • reducer function

apollo-link-state

  • mutation / query
  • resolver

Using Tags

              <Mutation
                mutation={mutations.ADD_ITEMS}
                variables={{ newItem: this.state.newItem }}
              >
                {addItem => (
                  <button style={styles.addButton} onClick={addItem}>
                    Add Item
                  </button>
                )}
              </Mutation>
              <Mutation mutation={mutations.CLEAN_ITEMS}>
                {cleanItems => (
                  <button style={styles.cleanButton} onClick={cleanItems}>
                    Clean Items
                  </button>
                )}
              </Mutation>

Cleaner & Familiar

    <ApolloProvider client={client}>
      <div style={styles}>
        <h2>Welcome to Apollo-Link-State</h2>
        <AddItems />
        <ListItems />
      </div>
    </ApolloProvider>

Cleaner Components


@graphql(addItem, { name: "addItem" })
@graphql(clearItems, { name: "clearItems" })
@graphql(updateNewItem, { name: "updateNewItem" })
@graphql(newItem)
export default class AddItems extends Component {
  // ... just access props
  // like `this.props.addItem`
}
const resolvers = {
  Mutation: {
    addItem: (_, _params, { cache }) => {
      const { items, newItem } = cache.readQuery({
        query: gql`
          {
            items @client
            newItem @client
          }
        `
      });
      const data = { items: items.concat([newItem]), newItem: "" };
      cache.writeData({ data });
      return null;
    },
    clearItems: (_, _params, { cache }) => {
      const data = { items: [] };
      cache.writeData({ data });
      return null;
    },
    updateNewItem: (_, { text }, { cache }) => {
      const data = { newItem: text }
      cache.writeData({ data });
      return null;
    },
  }
};
import { gql } from "apollo-boost";

// Queries
export const items = gql`
  {
    items @client
  }
`;

export const newItem = gql`
  {
    newItem @client
  }
`;

Cool ❄️

  • Remote/Local
  • Optimized
  • Online/Offline
  • Typed

Uncool 🔥

  • Decorator Magic
  • Can be Messy
  • Requires GraphQL knowledge

GraphQL Apollo-link-state

Describing Data

Types and Shape

Plenty More!

Review them all!

💪 Do you even test bro?

yes....ish

How?

Quick Tests:

README.md tests

  • Each example has a README.md
  • Each folder has a link to it from the main README
  • MORE?  Nope... but we could use more.

Slow Tests:

REACT

  • `yarn` each project
  • build each website
  • run `test` each project

Slow Tests:

REACT NATIVE

  • `npm` each project
  • bundle for iOS
  • bundle for Android

Slow means slowwwwww

~1hr to run

The Truth About State Management

Globals! ... Kidding but not really

YOU

  • Inspired to learn different state managers?
  • Inspired to contribute to the museum?
  • Inspired to create something similar?

10 examples

Originally

OVER 25 EXAMPLES

Now?

Thank you 20+ contributors!!!

#Hacktoberfest!

It's

Happy to Merge

You'll be helping us all

Thanks

Special

  • To the Conf

  • To Infinite Red

Thanks

Special

TO YOU!!!

GantLaborde

@

https://slides.com/gantlaborde/react-state-museum