+ - 0:00:00
Notes for current slide
Notes for next slide

Mutable or Immutable? Both!

ReactJS Day Verona 2017

Mattia Manzati - @mattiamanzati

Michel Weststrate - @mweststrate


1 / 47

2 / 47

3 / 47

How to do state management in React?

  1. setState
    Might be enough
  2. Redux
    You get paid per hour?
  3. MobX
    Stop the whitchcraft, heritic!
  4. jquery.data + refs
    You are my true hero

4 / 47

Comparing the opposite patterns

redux === immutable
mobx === mutable

5 / 47

Mutable and Immutable are for sure the current opposite paradigms, each one with its pros and its cons.

As developers we always try to find the ultimate solution, and the quest for that solution sometimes make us forget about what we learnt and approach new ways of doing things

So maybe instead of approaching a completely new paradigm, we should look behind, learn from our journey, and see if we can get the best of what we learnt

Mutable data structures are perfect for continuosly-changing values; forms for example are an example of highly-continuosly-changing data structures, that would require lot of new immutable objects to be allocated in memory as we interact with our forms.

On the other hand, Immutable structures are awesome, they allow easy testing and when they only contains serializable data they make easy to de/rehydrate our application state.

Redux

const initialState = {name: "", done: false}
function todoReducer(state = initialState, action){
if(!action) return state
switch(action.type){
case TOGGLE_TODO:
return ({...state, done: !state.done})
default:
return state
}
}
6 / 47

In Redux the reducer could be considered the core concept of data modeling, and that's predictable. Since we are dealing with immutable data, we could not change an instance, so the focus of our code will always be the data transformation rather than it's shape. Due to that, our data shape is kinda lost in all of that code.

Redux

const TOGGLE_TODO = 'todo/TOGGLE_TODO'
export function toggleTodo(){
return { type: TOGGLE_TODO }
}
7 / 47

And while we are at it, we could note that reducer and actions are'nt colocated, and that IMHO could potentially make our code less readable.

MobX

class Todo {
@observable name = ""
@observable done = false
@action toggle(){
this.done != this.done
}
}
8 / 47

Most of the people using MobX up to now used ES6 classes up to now, and that's nice. It lets you write clean code that express both data shape and actions in the same place making it highly readable.

MobX

Data shape is explicit

Redux

Data shape is implicit

9 / 47

Data Rehydration

const serverData = {
name: "Visit the opera!",
done: true
}
10 / 47

Redux

const newState = todoReducer(serverData, toggleTodo())
// newState.done === false
11 / 47

Since Redux uses immutable objects, they won't contain any application business logic like actions, computeds, etc... So it is really easy to rehydrate data onto our store, you just need to pass it onto your reducer.

MobX

class Todo {
// ...
@computed get snapshot(){
return {
name: this.name,
done: this.done
}
}
@action applySnapshot(snapshot){
this.name = snapshot.name
this.done = snapshot.done
}
}
12 / 47

Unfortunately, MobX uses classes, so it won't accept our server data, we need to define a property and a method to manage de/serialization.

MobX

(Usually) classes. Unlimited data shape. De/serialization is hard

Redux

Plain objects. Tree structure only. Serialization is done in actions

13 / 47

Redux state snapshots are awesome!

  • Allow time travel
  • Make testing easier
  • Better dev tools
14 / 47

What if we could get them for free?

Declare data shape

De/serialization becomes free

15 / 47

redux || mobx

true

mobx-state-tree

+

16 / 47
17 / 47
18 / 47

19 / 47

Demo

20 / 47

Static demo app, just json

Letś type this

Types

  • Describe the shape of the tree
  • Runtime checking
  • Designtime checking
  • Composable & Extensible
21 / 47

Types

Collection types
================

model
map
array
Primitive types
===============

string
literal
boolean
number
Date
reference
frozen
undefined
null

Higher Order Types
==================

enumeration
compose
union
optional
maybe
refinement
identifier
late
22 / 47

Demo

23 / 47

Connect demo to app store

Types

  • Enrich data
  • Actions
  • Views
  • Local / private state
  • Life cycle
24 / 47

Demo

25 / 47

Introduce actions

Connect observer

Actions

  • Can change own subtree
  • Discoverable
  • Bound
26 / 47

Demo

27 / 47

Add computed fields

Use filteredTodos

Views

  • Derived values
  • Memoized. Fully reacty. Powered by MobX
  • observer FTW
28 / 47

Demo

29 / 47

add local storage

Snapshots

Like a git commit

Describes the entire state at a specific moment in time

  • Immutable
  • Structurally shared
  • Free
  • Can be used to apply changes
    Reconciliation
30 / 47

Free: in perf costs, but also in effor: no need to produce a new state tree

31 / 47

32 / 47

Time Travelling!

33 / 47
import { onSnapshot, applySnapshot } from "mobx-state-tree"
const store = SomeModel.create()
const states = []
onSnapshot(store, snapshot => {
states.push(snapshot)
})
function undo() {
applySnapshot(store, states.pop())
}
34 / 47

Demo

35 / 47

add time traveller

const TimeTraveller = types.model({
history: types.optional(types.array(types.frozen), []),
undoIdx: -1,
targetPath: types.string
})
.actions(self => {
let targetStore, snapshotDisposer
return {
afterCreate() {
targetStore = resolvePath(self, self.targetPath)
snapshotDisposer = onSnapshot(targetStore, todos => {
self.history.push(snapshot)
self.undoIdx++
})
},
undo() {
applySnapshot(targetStore, self.history[--self.undoIdx])
},
beforeDestroy() {
snapshotDisposer()
}
}
})
36 / 47

Frozen type

local state

lifecycle hooks

Time travelling !== Undo / Redo

37 / 47

Demo

38 / 47

Add timeout stuff

Real undo / redo

  1. Record patches and inverse apply them
    Like git revert

  2. Record start state, rewind, replay all non-failing actions
    Like git rebase
39 / 47

Demo

40 / 47

Add undo / redo manager

Patches

Like a git patch

Describes the modifications from one commit to the next

  • Monitor fine grained changes
  • RFC-6902
  • Transmission ready
41 / 47
// ... like Time Traveller
afterCreate() {
const undoRedoMiddleware = createActionTrackingMiddleware({
onStart: call => recordPatches(call.tree),
onResume: (call, recorder) => recorder.resume(),
onSuspend: (call, recorder) => recorder.stop(),
onSuccess: (call, recorder) => {
self.history.push({
patches: recorder.patches,
inversePatches: recorder.inversePatches
})
},
onFail: (call, recorder) => recorder.undo()
})
addMiddleware(undoRedoMiddleware, targetStore)
}
42 / 47
function undo() {
applyPatch(targetStore, self.history[--self.undoIdx].inversePatches)
}
43 / 47

MST as Redux

  • state → snapshot
  • subscribeonSnapshot
  • dispatchapplyAction
  • connectobserver
44 / 47

MST as Redux

import {asReduxStore}
from 'mobx-state-tree/middleware/redux'
const reduxStore = asReduxStore(store)

45 / 47

Demo

46 / 47

connect devtools

npm install mobx-state-tree

@mweststrate - @mattiamanzati

47 / 47

2 / 47
Paused

Help

Keyboard shortcuts

, , Pg Up, k Go to previous slide
, , Pg Dn, Space, j Go to next slide
Home Go to first slide
End Go to last slide
b / m / f Toggle blackout / mirrored / fullscreen mode
c Clone slideshow
p Toggle presenter mode
t Restart the presentation timer
?, h Toggle this help
Esc Back to slideshow