Effective Application State Management - Lesson 2

Reading Time: 2 minutes

Prior Lesson

Compose your state

When you are going to introduce an application state you should know how to update it. If you're coming from a pure object oriented language like Java, you might think of a class (maybe using the singleton pattern), which instance(s) keep a list of key-value pairs. This might be sufficient for simple states, but usually you have nested objects, and it could be tedious to control this, as you need to introduce some abstractions and/or (de)serialization mechanism to deal with complex objects.

Fortunately, JavaScript Object Notation (JSON) is very handy and easy to compose. Instead of create some State Class you should directly work with the object.

var state = { items : [], user: { name: 'Oliver', isAdmin: true } }

It's trivial to add more properties:

state.newProp = { foo: 'bar' }

At this point I recommend to introduce an API to hide the state details. In further lessons I'll talk more about this and some of its implications, but for now I'll depict it in very simple manner.

class DeadSimpleStateManager{
    constructor(initialState = {}) {
        this.state = initialState 
    }
    
    update(newState){
        this.state = Object.assign(this.state, newState); /// composition
    }
    
    log() {
        console.log("State", JSON.stringify(this.state, ' ') );
    }
}

const mgr = new DeadSimpleStateManager();
mgr.update({a: 1});
mgr.log() // State: { a: 1 }

So, I'd like to draw attention to the update method, where we use object composition. Using Object.assign allows us to simply pass any kind of JSON, which will be composed to a new object then. This is still trivial, and this is how composition is done in general in JavaScript.

Merge vs Assign

But Object.assign has its own drawbacks:

  • It just copies the properties of the passed source objects
  • It does not truly merge the objects

Object.assign copies only own properties, and if these are objects, its references are copied only. So, it does not traverse the objects deeply, which results in a different behavior than maybe expected:

const mgr = new DeadSimpleStateManager();

mgr.update({a: 1});
mgr.log() // State: { a: 1 }
mgr.update({a: { f: 1 }} )
mgr.update({a: { g: 10 }} )
mgr.log() // State: { a: { g: 10} }

You can test it here, and if you need a true merge you need implementations like lodash.merge. Of course, a deep merge has impacts on performance, but with other techniques I'll talk in future lessons, we can at least partially compensate this issue. Overall, we should focus on performance only, when it matters (premature optimization)!

Facebooktwittergoogle_plusredditpinterestlinkedin

Leave a Reply

Your email address will not be published. Required fields are marked *