MobX

MobX

Externals

About The title

"Revenge Of The Observable!"

whomai

vitali.pe@gmail.com

 day 0

whomai

vitali.pe@gmail.com

 day 0

day 1546

MobX

MobX

A small library to manage state changes  automatically

MobX

A small library to manage state changes  automatically

MobX

A small library to manage state changes  automatically

What Is "state"?

What Is "state"?

the particular condition that something is in at a specific time

What Is "state"?

the particular condition that something is in at a specific time

What Is "state"?

the particular condition that something is in at a specific time

What Is "state"?

the particular condition that something is in at a specific time

structure + changes

data

What Is "state"?

the particular condition that something is in at a specific time

structure + changes

data

What Is "state"?

the particular condition that something is in at a specific time

structure + changes

data

React

Solves This Problem For UI State

Kinda...

UI Tree

UI Tree

UI Tree

UI Tree

Problem:

Problem:

Problem:

any nontrivial app state is a graph

TodoMVC

TodoMVC

Not Only React

Ideas

1. Manually compose data in the tree.

Ideas

1. Manually compose data in the tree.

Ideas

1. Manually compose data in the tree.

2. Use a DSL to query for data.

Use a DSL to query for data.

(for example GraphQL)

{
 user(id: 1) {
   age
   friends {
     name
   }
 }
}

UI Tree

{
 user(id: 42) {
   name
   friends {
     age
   }
 }
}
  • String based DSL
  • No clear schema (by design)
  • Works well on dense graphs
  • Solves a more general problem

Ideas

1. Manually compose data in the tree.

2. Use a DSL to query for data.

3. Save state outside of the tree.

Save state outside of the tree.

A.K.A "The Reagent Way"

(def click-count (r/atom 0))

(defn counting-component []
  [:div
   "The atom " [:code "click-count"] " has value: "
   @click-count ". "
   [:input {:type "button" :value "Click me!"
            :on-click #(swap! click-count inc)}]])

Save state outside of the tree.

A.K.A "The Reagent Way"

(def click-count (r/atom 0))

(defn counting-component []
  [:div
   "The atom " [:code "click-count"] " has value: "
   @click-count ". "
   [:input {:type "button" :value "Click me!"
            :on-click #(swap! click-count inc)}]])

Ideas

1. Manually compose data in the tree.

2. Use a DSL to query for data.

3. Save state outside of the tree.

4. Make it look like a tree.

Make it look like a tree

UI Tree

Make it look like a tree

"ViewModel" in MVVM

($scope)

"Cursors" (om, Reagent,etc)

(def foo-cursor (cursor app-state [:foo]))

(defn inside-foo-cursor []
  [:div (str "Inside foo-cursor: " @foo-cursor)])

MobX

A small library to manage state changes  automatically

MobX

4 simple consepts, no magic

Observable State

var me = mobx.observable({
   firstName : "vitali",
   lastName : "Perchonok",
   favoriteNumbers : [0], 
   age : 29
})

Observable State

var me = mobx.observable({
   firstName : "vitali",
   lastName : "Perchonok",
   favoriteNumbers : [0], 
   age : 29
})

mobx.autorun(
    () => console.log("your name:", 
                        me.firstName, me.lastName))

Observable State

var me = mobx.observable({
   firstName : "vitali",
   lastName : "Perchonok",
   favoriteNumbers : [0], 
   age : 29
})

mobx.autorun(
    () => console.log("your name:", 
                        me.firstName, me.lastName))

Observable State

var me = mobx.observable({
   firstName : "vitali",
   lastName : "Perchonok",
   favoriteNumbers : [0], 
   age : 29
})

mobx.autorun(
    () => console.log("your name:", 
                        me.firstName, me.lastName))
me.fullName = "Other";
// console: your name: Other Perchonok
me.lastName = "Name";
// console: your name: Other Name

me.age = 30;
me.favoriteNumbers.push(12);
// no effect

Observable State

Computed State

var me = mobx.observable({
   ....
   fullName : function() {
        return [this.firstName, this.lastName].join(" ")
    }
})

Computed State

var me = mobx.observable({
   ....
   fullName : function() {
        return [this.firstName, this.lastName].join(" ")
    }
})


mobx.autorun(() => console.log("your name:", me.fullName))

Computed State

var me = mobx.observable({
   ....
   fullName : function() {
        return [this.firstName, this.lastName].join(" ")
    }
})


mobx.autorun(() => console.log("your name:", me.fullName))

Computed State

var me = mobx.observable({
   ....
   fullName : function() {
        return [this.firstName, this.lastName].join(" ")
    }
})


mobx.autorun(() => console.log("your name:", me.fullName))

Computed State

var me = mobx.observable({
   ....
   fullName : function() {
        return [this.firstName, this.lastName].join(" ")
    }
})


mobx.autorun(() => console.log("your name:", me.fullName))

Computed State

var me = mobx.observable({
   ....
   fullName : function() {
        console.log("computed full name!");
        return [this.firstName, this.lastName].join(" ")
    }
})


mobx.autorun(() => console.log("your name:", me.fullName))
me.fullName = "Other";
// console: computed full name!
// console: your name: Other Perchonok

me.lastName = "Name";
// console: computed full name!
// console: your name: Other Name

me.fullName;
me.fullName;
// no effect

Computed State

me.fullName = "Other";
// console: computed full name!
// console: your name: Other Perchonok

me.lastName = "Name";
// console: computed full name!
// console: your name: Other Name

me.fullName;
me.fullName;
// no effect

Computed State

var me = mobx.observable({
  ...
  rename : action(function (first, last) {
        this.firstName = first;
        this.lastName = last;
    })
 
})

Actions

var me = mobx.observable({
  ...
  rename : action("rename user", 
    function (first, last) {
        this.firstName = first;
        this.lastName = last;
    })
 
})

Actions

var me = mobx.observable({
  ...
  rename : action("rename user", 
    function (first, last) {
        this.firstName = first;
        this.lastName = last;
    })
 
})

Actions

me.rename("Other", "name");
// console: computed full name!
// console: your name: Other Name

me.fullName;
me.fullName;
// no effect

Actions

me.rename("Other", "name");
// console: computed full name!
// console: your name: Other Name


mobx.useStrict(true);

me.firstName = "boo"; // throws:
// [mobx] Invariant failed: It is not allowed to 
// create or change state outside an `action`

Actions

class Person {
   @observable firstName = "vitali";
   @observable lastName = "Perchonok";
   ...

   @computed get fullName() {
        return [this.firstName, this.lastName].join(" ");
    }

   @action("rename user") rename(first, last) {
        this.firstName = first;
        this.lastName = last;
    }
}

ES6+

MobX

4 simple consepts, no magic

Simple things should be simple, complex things should be possible.
Alan Kay

+

"mobx-react"


class Crap extends React.Component {

  render() {
   
      return <Layout>
              <Component data={this.props.user}/>
              <SimpleWidget />
      </layout>

  }
}

export default observer(Crap);

observer()


class Crap extends React.Component {

  render() {
   
      return <Layout>
              <Component data={this.props.user}/>
              <SimpleWidget />
      </layout>

  }
}

export default observer(Crap);

observer()

var StatelessCrap = observer(({user}) => {
      return <Layout>
              <Component data={user}/>
              <SimpleWidget />
      </layout>

})

export default StatelessCrap ;

observer()

@observer
class Crap extends React.Component {

  render() {
   
      return <Layout>
              <Component data={this.props.user}/>
              <SimpleWidget />
      </layout>

  }
}

export default Crap;

@observer

@observer
class Crap extends React.Component {

  render() {
   
      return <Layout>
              <Component data={this.props.user}/>
              <SimpleWidget />
      </layout>

  }
}

export default Crap;

@observer

@observer

makes render() autorun

@observer

makes render() autorun

adds "pure render" mixin


class Counter extends React.Component {
 
  @observable times = 0

  render() {
      var addOne = ()=> this.times++; 

      return <div>
        <label>clicked {this.times} times</label>
        <button onClick={addOne}>+1</button>
    </div>

  }
}

Local State


class Counter extends React.Component {
 
  @observable times = 0

  render() {
      var addOne = ()=> this.times++; 

      return <div>
        <label>clicked {this.times} times</label>
        <button onClick={addOne}>+1</button>
    </div>

  }
}

Local State

Questions?

MobX

By Vitali Perchonok