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
The React State Museum
By Gant Laborde
The React State Museum
In React there's a plethora of state management systems. How can you choose one over any other? Let me help!
- 2,683