Skip to content

Instantly share code, notes, and snippets.

@lxynox
Last active May 29, 2020 04:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lxynox/9dd496d9f2ae6a96c2f4eb9b2fa899ea to your computer and use it in GitHub Desktop.
Save lxynox/9dd496d9f2ae6a96c2f4eb9b2fa899ea to your computer and use it in GitHub Desktop.
Front-end (react) state management solutions

mobx-state-tree is a state container that combines the simplicity and ease of mutable data with the traceability of immutable data and the reactiveness and performance of observable data.

How???

feature/
  state/
    - index.js
  - component.jsx

state/index.js

import { types } from "mobx-state-tree"

const initState = () => ({...})
export default types.model(initState())
  .views(self => ...)
  .actions(self => ...)

component.jsx

import { observer } from 'mobx-react'
export default observer(() => ...)

Pros & Cons

Pros:

  • best of both worlds (mutable & immutable)
  • opinionated

Cons:

  • learning curve

mobx data flow

action-state-view

Concepts

  • state
  • derivations
  • actions

How???

feature/
  state/
    - index.js
  - component.js

state/index.js

import { observable, action, computed } from 'mobx'

export default class Store {
  @observable field
  @action setField(val) { this.field = val }
  @computed getField() { return this.field }
  ...
}

component.js

import { observer, inject } from 'mobx-react'

@inject('store')
@observer
export default extends React.Component {
  ...
  render() {
    const { store } = this.props
  }
}

Pros & Cons

Pros:

  • separation of concerns (action/state/derivations)
  • simplicity
  • FRP(functional reactive programming) offers reactiveness and performance for free

Cons:

  • Implicitness

How???

Structure

feature/
  state/
    - context.js
  - Container.jsx
  - Provider.jsx

context.js

const C = React.createContext(null)

export const Provider = props => {
  const state = {...}
  return <C.Provider value={state}r>{props.children}</C.Provider>
}

export const Consumer = props => {
  const value = React.useContext(C)
  if (value === null || typeof props.children !== 'function') throw Error(...)
  return props.children(value)
  // Or
  return <C.Consumer>{props.children}</C.Consumer>
}

Might be good enough for most cases, lots of flexibility due to react hooks

How???

feature/
  state/
    -action.js 
    -reducer.js
    -selector.js
  -Container.jsx (requires react-redux binding)

Design constraints

  • Action must contain "type" property, and be serializable
  • Reducer must be pure functions and immutable

Trade-offs

  • trade learning curve for traceability
  • trade verbosity for explicitness

Pros & Cons

Pros:

  • separation of concerns (action/reducer/selector)
  • traceability
  • ecosystem & tooling
  • adoption rate

Cons:

  • boilerplates
  • global state doesn't fit all
  • learning curve

How???

Structure

feature/
  state/
    -container.js
  -Container.jsx
  -Provider.jsx

state/container.js

import {Container} from 'unstate'
class MyContainer extends Container {
  state = {}
  onChange = (e) => this.setState(...)
}

Container.jsx

import {Subscribe} from 'unstate'
import MyContainer from './state/container'
...
return <Subscribe to=[MyContainer]>{instance => renderSomething(instance.state)}</Subscribe>

Provider.jsx

import {Provider} from 'unstate'
import Container from './Container.jsx'
...
return <Provider><Container /></Provider>

Constraints

  • Each container has to be a subclass of base container class (event emitter)

Pros & Cons

Pros:

  • "Container" mimics react's setState API, similar to react mental model
  • unopinionated

Cons:

  • unopinionated
  • "Container" introduces extra layer of complexity

How???

Structure

feature/
  state/
    -container.js
  -Container.jsx
  -Provider.jsx

state/container.js

import { createContainer } from "unstated-next"
function useState(initialState = 0) {
  return React.useState(initialState)
  // or
  let [count, setCount] = useState(initialState)
  let decrement = () => setCount(count - 1)
  let increment = () => setCount(count + 1)
  return { count, decrement, increment }
}
export createContainer(useState)

Container.jsx

import Container from './state/container'
export default props => {
  const {state, ...} = Container.useContainer({})
  return ...
}

Provider.jsx

import {Provider} from './state/container'
import Container from './Container.jsx'
...
return <Provider><Container /></Provider>

Pros & Cons

Pros:

  • just "react" (like react router 4+)
  • simplicity
  • unopinionated

Cons:

  • unopinionated (how does it scale project/team wise)
@lxynox
Copy link
Author

lxynox commented Dec 15, 2019

Unsolved problems

  • Data caching
  • Cache invalidation
  • State lifetime (global vs local)

How???

@lxynox
Copy link
Author

lxynox commented May 29, 2020

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