Hot File

Manage Your JavaScript Application State with MobX

View: 194    Dowload: 0   Comment: 0   Post by: hanhga  
Author: none   Category: Javascript   Fields: Other

9 point/2 review File has been tested

This article was peer reviewed by Michel Weststrate and Aaron Boyer. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be!

Introduction

This article was peer reviewed by Michel Weststrate and Aaron Boyer. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be!

If you’ve ever written anything more than a very simple app with jQuery, you’ve probably run into the problem of keeping different parts of the UI synchronized. Often, changes to the data need to be reflected in multiple locations, and as the app grows you can find yourself tied in knots. To tame the madness, it’s common to use events to let different parts of the app know when something has changed.

So how do you manage the state of your application today? I’m going to go out on a limb and say that you’re over subscribing to changes. That’s right. I don’t even know you and I’m going to call you out. If you’re not over subscribing, then I’m SURE you’re working too hard.

Unless you’re using MobX of course…

What is “State” Anyway?

Here’s a person. Hey, that’s me! I have a firstNamelastName and age.
In addition, the fullName() function might come out if I’m in trouble.

var person = {
  firstName: 'Matt',
  lastName: 'Ruby',
  age: 37,
  fullName: function () {
    this.firstName + ' ' + this.lastName;
  }
};

How would you notify your various outputs (view, server, debug log) of modifications to that person? When would you trigger those notifications? Before MobX, I would use setters that would trigger custom jQuery events or js-signals. These options served me well, however, my usage of them was far from granular. I would fire one “changed” event if any part of the personobject changed.

Let’s say I have a piece of view code that shows my first name. If I changed my age, that view would update as it was tied to that person‘s changed event.

person.events = {};

person.setData = function (data) {
  $.extend(person, data);
  $(person.events).trigger('changed');
};

$(person.events).on('changed', function () {
  console.log('first name: ' + person.firstName);
});

person.setData({age: 38});

How could we tighten that over-fire up? Easy. Just have a setter for each field and separate events for each change. Wait–with that you may start over-firing if you wanted to change both age and firstName at once. You’d have to create a way to delay your events from firing until both changes completed. That sounds like work and I’m lazy…

MobX to the rescue

MobX is a simple, focused, performant and unobtrusive state management library developed by Michel Weststrate.

From the MobX docs:

var person = mobx.observable({
  firstName: 'Matt',
  lastName: 'Ruby',
  age: 37,
  fullName: function () {
    this.firstName + ' ' + this.lastName;
  }
});

Notice the difference? mobx.observable is the only change I’ve made.
Let’s look at that console.log example again:

mobx.autorun(function () {
  console.log('first name: ' + person.firstName);
});

person.age = 38; // prints nothing
person.lastName = 'RUBY!'; // still nothing
person.firstName = 'Matthew!'; // that one fired

Using autorun, MobX will only observe what has been accessed.

If you think that was neat, check this out:

mobx.autorun(function () {
  console.log('Full name: ' + person.fullName);
});

person.age = 38; // print's nothing
person.lastName = 'RUBY!'; // Fires
person.firstName = 'Matthew!'; // Also fires

Intrigued? I know you are.

Core MobX concepts

observable

var log = function(data) {
  $('#output').append('<pre>' +data+ '</pre>');
}

var person = mobx.observable({
  firstName: 'Matt',
  lastName: 'Ruby',
  age: 34
});

log(person.firstName);

person.firstName = 'Mike';
log(person.firstName);

person.firstName = 'Lissy';
log(person.firstName);

MobX observable objects are just objects. I’m not observing anything in this example. This example shows how you could start working MobX into your existing codebase. Just use mobx.observable() or mobx.extendObservable() to get started.

autorun

var person = mobx.observable({
  firstName: 'Matt',
  lastName: 'Ruby',
  age: 0
});

mobx.autorun(function () {
  log(person.firstName + ' ' + person.age);
});

// this will print Matt NN 10 times
_.times(10, function () {
  person.age = _.random(40);
});

// this will print nothing
_.times(10, function () {
  person.lastName = _.random(40);
});

You want to do something when your observable values change, right? Allow me to introduce autorun(), which will trigger the callback whenever a referenced observable changes. Notice in the above example how autorun() will not fire when ageis changed.

computed

var person = mobx.observable({
  firstName: 'Matt',
  lastName: 'Ruby',
  age: 0,
  get fullName () {
    return this.firstName + ' ' + this.lastName;
  }
});
log(person.fullName);

person.firstName = 'Mike';
log(person.fullName);

person.firstName = 'Lissy';
log(person.fullName);

Run on CodePen

See that fullName function and notice how it takes no parameters and the get? MobX will automatically create a computed value for you. This is one of my favorite MobX features. Notice anything weird about person.fullName? Look again. That’s a function and you’re seeing the results without calling it! Normally, you would call person.fullName() notperson.fullName. You’ve just met your first JS getter.

The fun doesn’t end there! MobX will watch your computed value’s dependencies for changes and only run when they have changed. If nothing has changed, a cached value will be returned. See the case below:

var person = mobx.observable({
  firstName: 'Matt',
  lastName: 'Ruby',
  age: 0,
  get fullName () {
    // Note how this computed value is cached.
    // We only hit this function 3 times.
    log('-- hit fullName --');
    return this.firstName + ' ' + this.lastName;
  }
});

mobx.autorun(function () {
  log(person.fullName + ' ' + person.age);
});

// this will print Matt Ruby NN 10 times
_.times(10, function () {
  person.age = _.random(40);
});

person.firstName = 'Mike';
person.firstName = 'Lissy';

Here you can see that I’ve hit the person.fullName computed many times, but the only time the function is run is when either firstName or lastName are changed. This is one of the ways that MobX can greatly speed up your application.

MORE!

I’m not going to continue re-writing MobX’s terrific documentation any longer. Look over the docs for more ways to work with and create observables.

Putting MobX to work

Before I bore you too much, let’s build something.

Here’s a simple non-MobX example of a person that will print the person’s full name whenever the person changes.

Let’s build something slightly less trivial:

// observable person
var person = mobx.observable({
  firstName: 'Matt',
  lastName: 'Ruby',
  age: 37
});

// reduce the person to simple html
var printObject = function(objectToPrint) {
  return _.reduce(objectToPrint, function(result, value, key) {
    result += key + ': ' + value + '<br/>';
    return result;
  }, '');
};

// print out the person anytime there's a change
mobx.autorun(function(){
  $('#person').html(printObject(person));
});

// watch all the input for changes and update the person
// object accordingly.
$('input').on('keyup', function(event) {
  person[event.target.name] = $(this).val();
});

Run on CodePen

Here we’re able to edit the whole person object and watch the data output automatically. Now there are several soft spots in this example, most notably that the input values are not in sync with the person object. Let’s fix that:

mobx.autorun(function(){
  $('#person').html(printObject(person));
  // update the input values
  _.forIn(person, function(value, key) {
    $('input[name="'+key+'"]').val(value);
  });
});

Run on CodePen

I know, you have one more gripe: “Ruby, you’re over rendering!” You’re right. What you’re seeing here is why many people have chosen to use React. React allows you to easily break your output into small components that can be rendered individually.

For completeness sake, here’s a jQuery example that I’ve optimized.

Would I do something like this in a real app? Probably not. I’d use React any day if I needed this level of granularity. When I’ve used MobX and jQuery in real applications, I use autorun()s that are granular enough that I’m not re-building the whole DOM on every change.

You’ve made it this far, so here’s the same example built with React and MobX

Let’s Build a Slideshow

How would you go about representing the state of a slideshow?
Let’s start with the individual slide factory:

var slideModelFactory = function (text, active) {
  // id is not observable
  var slide = {
    id: _.uniqueId('slide_')
  };

  return mobx.extendObservable(slide, {
    // observable fields
    active: active || false,
    imageText: text,
    // computed
    get imageMain() {
      return 'https://placeholdit.imgix.net/~text?txtsize=33&txt=' + slide.imageText + '&w=350&h=150';
    },
    get imageThumb() {
      return 'https://placeholdit.imgix.net/~text?txtsize=22&txt=' + slide.imageText + '&w=400&h=50';
    }
  });
};

We should have something that will aggregate all of our slides. Let’s build that now:

var slideShowModelFactory = function (slides) {
  return mobx.observable({
    // observable
    slides: _.map(slides, function (slide) {
      return slideModelFactory(slide.text, slide.active);
    }),
    // computed
    get activeSlide() {
      return _.find(this.slides, {
        active: true
      });
    }
  });
};

The slideshow lives! This is more interesting because we have an observable slides array that will allow us to add and remove slides from the collection and have our UI update accordingly. Next, we add the activeSlide computed value that will keep itself current as needed.

Let’s render our slideshow. We’re not ready for the HTML output yet so we’ll just print to console.

var slideShowModel = slideShowModelFactory([
  {
    text: 'Heloo!',
    active: true
  }, {
    text: 'Cool!'
  }, {
    text: 'MobX!'
  }
]);

// this will output our data to the console
mobx.autorun(function () {
  _.forEach(slideShowModel.slides, function(slide) {
    console.log(slide.imageText + ' active: ' + slide.active);
  });
});

// Console outputs:
// Heloo! active: true
// Cool! active: false
// MobX! active: false

Cool, we have a few slides and the autorun just printed out their current state. Let’s change a slide or two:

slideShowModel.slides[1].imageText = 'Super cool!';
// Console outputs:
// Heloo! active: true
// Super cool! active: false
// MobX! active: false

Looks like our autorun is working. If you change anything that autorun is watching, it will fire. Let’s change our output derivation from the console to HTML:

var $slideShowContainer = $('#slideShow');
mobx.autorun(function () {
  var html = '<div class="mainImage"><img src="' 
           + slideShowModel.activeSlide.imageMain 
           + '"/></div>';

  html += '<div id="slides">';
  _.forEach(slideShowModel.slides, function (slide) {
    html += '<div class="slide ' + (slide.active ? ' active' : '') 
         + '" data-slide-id="' + slide.id + '">';
    html += '<img src="' + slide.imageThumb + '"/>'
    html += '</div>';
  });
  html += '</div>';
  $slideShowContainer.html(html);
});

We now have the basics of this slideshow displaying, however, there’s no interactivity yet. You can’t click on a thumbnail and change the main image. But, you can change the image text and add slides using the console easily:

// add a new slide
slideShowModel.slides.push(slideModelFactory('TEST'));
// change an existing slide's text
slideShowModel.slides[1].imageText = 'Super cool!';

Let’s create our first and only action in order to set the selected slide. We’ll have to modify slideShowModelFactory by adding the following action:

// action
setActiveSlide: mobx.action('set active slide', function (slideId) {
  // deactivate the current slide
  this.activeSlide.active = false;
  // set the next slide as active
  _.find(this.slides, {id: slideId}).active = true;
})

Why use an action you ask? Great question! MobX actions are not required, as I’ve shown in my other examples on changing observable values.

Actions help you in a few ways. First, MobX actions are all run in transactions. What that means is that our autorun and other MobX reactions, will wait until the action has finished before firing. Think about that for a second. What would have happend if I tried to deactivate the active slide and activate the next one outside of a transaction? Our autorun would have fired twice. The first run would have been pretty awkward, as there would have been no active slide to display.

In addition to their transactional nature, MobX actions tend to make debugging simpler. The first optional parameter that I passed into my mobx.action is the string 'set active slide'. This string may be output with MobX’s debugging APIs.

So we have our action, let’s wire up its usage using jQuery:

$slideShowContainer.on('click', '.slide', function () {
  slideShowModel.setActiveSlide($(this).data('slideId'));
});

 

Manage Your JavaScript Application State with MobX

Manage Your JavaScript Application State with MobX Posted on 12-10-2016  This article was peer reviewed by Michel Weststrate and Aaron Boyer. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be! 4.5/10 194

Comment:

To comment you must be logged in members.

Files with category

  • JUnit 5 State Of The Union using java

    View: 367    Download: 0   Comment: 0   Author: none  

    JUnit 5 State Of The Union using java

    Category: Javascript
    Fields: Other

    4.5/1 review
    JUnit 5 has been under development for about 14 months now and the prototype is almost a year old. Time to summarize what happened so far, where the project stands, and where it’s going.

  • Getting Started with Dropwizard using java

    View: 362    Download: 0   Comment: 0   Author: none  

    Getting Started with Dropwizard using java

    Category: Javascript
    Fields: Other

    4.5/1 review
    Dropwizard is a framework for building RESTful web services in Java. In this tutorial we’re going to have a look at how to get started with developing a Dropwizard application by building a new service from scratch.

  • Build Query NULL Value in MySql

    View: 190    Download: 0   Comment: 0   Author: none  

    Build Query NULL Value in MySql

    Category: Javascript
    Fields: Other

    2.5/2 review
    Misunderstanding NULL is common mistake beginners do while writing MySql query. While quering in MySql they compare column name with NULL. In MySql NULL is nothing or in simple word it isUnknown Value so if you use comparison operator for NULL values...

  • Manage Your JavaScript Application State with MobX

    View: 194    Download: 0   Comment: 0   Author: none  

    Manage Your JavaScript Application State with MobX

    Category: Javascript
    Fields: Other

    2.25/2 review
    This article was peer reviewed by Michel Weststrate and Aaron Boyer. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be!

  • Build Bringing Pages to Life with the Web Animations API

    View: 204    Download: 0   Comment: 0   Author: none  

    Build Bringing Pages to Life with the Web Animations API

    Category: Javascript
    Fields: Other

    4.5/2 review
    This article is by guest author Dudley Storey. SitePoint guest posts aim to bring you engaging content from prominent writers and speakers of the JavaScript community.

  • How to Style Google Custom Search Manually

    View: 179    Download: 0   Comment: 0   Author: none  

    How to Style Google Custom Search Manually

    Category: Javascript
    Fields: Other

    0/0 review
    Website owners very often decide on using Google’s Custom Search Engine (GCSE) for searching through their content instead of using built-in and/or custom search functionality. The reason is simple – it’s much less work, and most often it does the...

  • Test React Components Using Jest

    View: 4795    Download: 0   Comment: 0   Author: none  

    Test React Components Using Jest

    Category: Javascript
    Fields: Other

    4.5/1 review
    This article is by guest author Jack Franklin. SitePoint guest posts aim to bring you engaging content from prominent writers and speakers of the JavaScript community.

  • Programming Question Reverse String without using String function

    View: 312    Download: 0   Comment: 0   Author: none  

    Programming Question Reverse String without using String function

    Category: Javascript
    Fields: Other

    0/0 review
    Write a program to reverse string without using string function. You don’t have to use any in-built string library function. This problem can be solved by multiple approaches. Let’s check it.

 
Newsletter Email

File suggestion for you

File top downloads

logo codetitle
Codetitle.com - library source code to share, download the file to the community
Copyright © 2015. All rights reserved. codetitle.com Develope by Vinagon .Ltd