Fun State Management With Ember and Microstates

Brandyn Bennett
8 min readAug 14, 2019

--

For the past 8 months my team and I have been using Microstates.js in our Ember.js application to make state management more fun. I’m going to tell you about what motivated our decision, what the payoffs have been, and help you do it too.

Motivation

UX Design of table populated with financial data.

I work at Allovue, an education finance company, that gets more money to K-12 students by helping public school districts understand, manage, and budget their money. Because we deal with financial data we tend to have many tables in our UI.

The 20+ tables we show our users all require some common functionality besides simply showing rows and columns. Each table needs to be filterable, sortable, and paginated. With just those 3 features there’s quite a bit of state to manage. We have to know which columns should be sorted and in what directions, what columns have filters applied, what filter operators are used, how many items per page to show, etc. To further complicate matters, some of these pieces of state are dependent on one another. For example, if I change the sort direction I need to reset the pagination to page 1.

How would you implement all those features using Data Down Actions Up (DDAU)? You can do it, but it gets a bit messy and is less fun.

So Many Arguments

We have a contextual component for our tables that makes it possible to pre-wire these related components with the necessary data and actions. However, the API for this contextual component is a bit unwieldy. We have to pass in each piece of state along with the action for updating that state.

We want to show parts of this state as query params in the url to make it shareable between users. Meeting this requirement means having controller.js own the data rather than the <Table> component.

Grouping Related State

To reduce the number of individual actions and state being passed through it may be a good idea to group related data together into an object to pass through. This may give you something more like this:

This definitely looks more manageable, however now there are some new problems:

Keeping Stuff In Sync

Our customers have lots of data which means that we can’t load it all at once from the server. To keep the page feeling snappy we have to paginate the data and only load a portion at a time. The server does the pagination for us since it knows about all the data and is faster. This means that if a sort changes, or a filter changes, we have to refetch our data. We don’t want the entire route to rerender every time the state of the table changes, instead we want the <Table/> to load it’s own data and show a loading spinner while that happens.

To do that we put a didReceiveAttrs hook inside of <Table/> and perform an ember-concurrency task anytime the state changes.

This is all well and good, so let’s try and update a piece of state. Let’s say we want to change the sort direction on a column. The natural thing an Ember developer might write for the sort component action would be something like this:

Congratulations, you have just violated DDAU and have mutated some state you didn’t own. It was WAY too easy to do that! This will not trigger our didReceiveAttrs hook because it only runs when an entire object reference changes, not just a property of the object.

Additionally, if you have any computed properties that look like this:

Those aren’t going to update either. You’d have to specify each part of the dependent key you care about 'sort.{dir,column}'.

In order to get didReceiveAttrs to fire and have simple computed property definitions, we have to replace the ENTIRE sort object that is being passed in. So how do we do that?

In our controller we’d need to have an action that does something like this:

And our component that’s using this action would have to pass in an object like this:

Why Is This So Hard? 😫

All I want to do is reload my data anytime the state of the table changes. If it was just one piece of state it wouldn’t be so bad. However, the reality in most apps is that there’s a lot more state than that to manage. This means that for every, single piece of state in my app that needs to behave this way I’ll have to:

  • Create my state object and pass it to my components
  • Create an action that copies a new state object and pass that action to my components
  • Use my action in a child component and pass it an entire object instead of just the property I care about
  • Make sure my computed properties all specify the properties they care about on the state object instead of just the object itself

The worst part about this is that even if I go through all these hoops, there’s no guarantee that another developer won’t do set(this.sorts, 'dir', dir) and introduce a bug later on. It feels very natural and good to mutate state in this way. Expecting future developers (including myself) to remember to go against this intuitive API sets them up to do the wrong thing.

Microstates to the Rescue

Fortunately for me, I listen to the Frontside Podcast and heard about this very interesting library called Microstates.js which addresses these issues.

Immutability

What we’re really after here is immutability. We want our table’s state to act more like a movie than an individual picture. Meaning we don’t want to mutate the properties of the state in-place to update the view, we want to replace it entirely. This way, any change to the state will make all our didReceiveAttrs hooks and computed properties update, without us having to listen to every single property in the object.

Microstates allows you to create state objects that are ONLY immutable. You can’t change them without generating a brand new one.

So if you have this:

This will create a new Table state object that is sorted ascending. If we want to change to a descending sort we can do this:

Now at this point you might expect the variable table to be sorted desc now. But you’d be wrong. Microstates are immutable, which means executing a change has no effect on the original. You’d need to do something like this instead:

While this is different than what we’re used to, there are some really awesome payoffs.

Stuff Stays In Sync

Because any change in our Table state tree creates an entirely new object now we can be sure that our didReceiveAttrs hooks and computed properties will update on every change. We no longer have to specify every key we want to watch, instead we can just specify the tableState property.

Enforcement of DDAU

In Emberland™️ we are proponents of DDAU and have found it to be a useful pattern, however the framework itself doesn’t do very much to force us to do it. It was very easy in my previous examples to call set(this.sorts, ‘dir’, ‘desc’) and mutate a passed in object without using an action.

By using a Microstate you can enforce DDAU in your components. Because each transition method on a microstate returns a completely new object a child component can’t mutate the original object passed to it from a parent. It is therefore forced to use an action if it wants to update the data.

The tradeoff with DDAU is that sometimes it can make maintenance more difficult when we have to pass both the data and an action to update that data all the way through our component hierarchy. Microstate objects have transition methods on them that are type specific (Boolean has toggle, Array has push, etc.), so why should we need the overhead of passing an additional property just to update the parent?

Fortunately, the maintainers of Microstates.js are oh so thoughtful and have created a way to negate this tradeoff.

Immutable State with Mutable-like Syntax

Instead of having to do the dance of calling a transition method, passing the newly generated object up to an action, and then passing it back down, Microstates.js provides us with a Store function. This is not a Store like the EmberData store, though the name makes it sound like that. Instead it is a function that takes a microstate object and then invokes a callback anytime that object transitions to a new state.

This means that instead of needing to pass each new microstate object up via an action you will automatically get your new state passed down to your components when a transition happens.

The Store api is a bit low-level and would be tedious to use all the time. Fortunately the ember-addon wrapper of Microstates.js provides us with 2 higher-level APIs for creating microstates this way:

  • The {{state}} helper for templates
  • The state() computed property macro for JS

The State Helper

The {{state}} helper can be used by itself or in conjunction with the {{type}} helper the addon also provides. The {{type}} helper will allow you to use custom Microstate classes you’ve defined and expects them to live in app/types.

The State Computed Property Macro

The state computed property macro is useful when you need a microstate to be created in a JS file. It expects an already created Microstate to be passed to it and then takes care of updating itself on each transition.

One challenge to be aware of with the macro is that you can’t reference other properties in your component to create the initial state value. You’ll have to create that outside the macro definition and use a transition method to update the microstate you created with the macro.

So for example, this won’t work:

Instead you’d have to do something more like this:

Much Less Boilerplate

When you leverage the Store, {{state}} helper, or state macro you get some nice benefits. One is that you don’t have to sprinkle your app with actions each time you want to update a simple property. The Store puts a closure around your state object so you can call its transition methods directly in the template.

So instead of doing this:

You can now simply do:

Conclusion

Setting up a reasonably sized data structure to hold state for our tables was a bit painful out of the box. We ran into some problems:

  • Our component’s argument list got large as we had to pass in both actions and properties for each piece of state we needed
  • Putting all the state in one object or some smaller objects was also difficult because A. Ember doesn’t have a convention around where to put these type of objects and B. our computed properties and didReceiveAttrs hooks became difficult to maintain.
  • It became easy for child components to mutate state they didn’t own and create confusing situations.

Microstates solved these problems for us:

  • They gave us a way to create an immutable data structure that would update our computed properties on each change and could not be mutated by child components
  • They gave us a dedicated place to put our state objects in the types directory
  • They gave us helpers and macros that would automatically update our component properties when our state changed and thus eliminated the need to pass an action for each piece of state

What’s Next?

In the next article I’m going to talk about some tips and tricks for using Microstates in your app as well as how to avoid some “gotchas”.

--

--