Let’s build a Time Machine

Nir Yosef
6 min readJan 22, 2018

How many times a day do people ask you if you can travel back in time? If you are building a state management library, the answer is quite a lot.

In this tutorial I will show you how to achieve time traveling and state serialization capabilities using Controllerim.

If you don’t know what Controllerim is, you better start by reading Getting started with Controllerim and The Ugly Side Of Redux

A Small Disclaimer

I actually don’t think that time traveling is very useful for debugging. Redux has put time traveling capabilities under the spotlight, and it seems like everyone loves it. I can bet that less than 5% of Redux users have ever used time traveling for debugging, and most of those who did haven’t used it more than once (we are in the disclaimer part, so I’ll have to admit that my statistics are based on nothing but asking few colleagues at work and a hunch). But anyway, let’s face it- It’s a pretty cool feature indeed! If I was asked to choose the main reasons for justifying the development of a feature, I would say:

  1. It will bring you money.
  2. It will help humanity.
  3. It’s freaking cool.

So, with rule #3 in our pocket, we can go ahead and build our first time machine with Controllerim!

Time traveling and state tree viewer with Controllerim

To see a working example click here.

The secret of time traveling mechanism

Time traveling is actually just a one cool side effect of a more bigger and important concept- state serialization.

In his article You Might Not Need Redux, Dan Abramov enumerate some advantages of maintaining a serializable state:

1. Persist state to a local storage and then boot up from it, out of the box.

2. Pre-fill state on the server, send it to the client in HTML, and boot up from it, out of the box.

3. Serialize user actions and attach them, together with a state snapshot, to automated bug reports, so that the product developers can replay them to reproduce the errors.

4. Maintain an undo history or implement optimistic mutations without dramatic changes to how the code is written.

5. Travel between the state history in development, and re-evaluate the current state from the action history when the code changes, a la TDD.

If your state is easily serializable, you get all this stuff almost out of the box, and this is also the situation with Controllerim.

So let’s move on and dive into the technical details.

The state tree

Controllerim holds behind the scenes a tree-like representation of the app’s state. Let’s assume that our app is structured like this:

<Root>
<Parent>
<Child/>
</Parent>
</Root>

And let’s assume that every component has a corresponding controller. The state tree will look like this:

{
name: "RootController",
serialID: "0",
state: {...},
children: [{
name: "ParentController",
serialID: "1",
state: {...},
children: [{
name: "ChildController",
serialID: "2",
state: {...},
children: []
}]
}]
}

When you call to getStateTree() inside one of your controllers, you get a state tree snapshot with the current controller as the root. For example, if we call getStateTree() inside ParentController, we’ll get this snapshot:

{
name: "ParentController",
serialID: "1",
state: {...},
children: [{
name: "ChildController",
serialID: "2",
state: {...},
children: []
}]
}

So for creating our little time machine, we are going to replace the app’s root with our own DevTools component so we could gain access to the state tree of the whole app, listen on every change to the state tree, save a snapshot using getStateTree(), and finally load it when needed using setStateTree().

The serialID prop

In the example above you can see that every node in the tree has a serialID prop. This prop helps the setStateTree() function to match the correct state to the corresponding instance when there are multiple instances of the same component on the screen. Controllerim can automatically manage the serialID for you- The DOM is already a tree like structure, so in theory it isn’t a problem to match the correct data to the corresponding child based on the child’s index inside its parent. The problem is that React doesn’t easily let us know the child‘s index in its parent, so Controllerim uses a bit nasty workaround to infer the order of the children. Because I am still not sure if this workaround works 100% of the time, Controllerim will currently use this auto indexing mechanism only if you explicitly turn it on. For creating a dev tools, this auto indexing mechanism is reliable enough, but if you need to use setStateTree() to load a snapshot in production, for now it will be safer if you explicitly supply the serialID for each instance of your smart components manually:

<Root serialID="root">
<Parent serialID="parent">
<Child serialID="child1"/>
<Child serialID="child2"/>
</Parent>
</Root>

I know it looks like a lot of overhead, but it’s better to play it safe in production, and anyway most apps don’t really need to load snapshots in production. In the future I hope that the auto indexing mechanism will be proved to be stable and we could use it also in production.

Let’s see some code

Our DevTools component will be a higher order component. For adding the dev tools to your app you will need to wrap your root component within a DevTool component like so:

<DevTools><App/></DevTools>

After wrapping your root of your app with the DevTools component, you will see the bottom panel of the devTools with the time machine slider and the state tree viewer.

DevTools.js:

What we are seeing here is a bit simplified version of the final DevTools component (I removed some edge cases handling from the code). Let’s break it down:

  • Line 6: we turn on the auto indexing mechanism by calling useExperimentalSerialization.
  • Line 21: We are adding a state tree listener so we could save a snapshot of the tree on every change. The data argument is a regular JSON object.
  • Line 34: On each change of the slider, we load the correct snapshot by calling setStateTree().
  • Line 41: We render the children at the upper side of the screen and the dev tools panel at the lower side of the screen.
  • Line 46: This is a sweet one. We print the state tree object to the screen so we could see how the state tree looks like at any given moment. The cool thing is that the state tree object that is being returned by getStateTree() is an observable by itself, just like the controller’s state, so any changes in the state tree will be reflected immediately by the view!

And that’s it!, We have just created our first (yet very basic) dev tools with time machine capabilities!

A complete example of the DevTools with the css and some better edge cases handling can be found here: DevTools Example.

A quick recap

Our DevTools component is replacing the root of the app, so the state tree of the DevTools controller is the state tree of the entire app. We attached a state tree listener so we could save a snapshot of the state tree on every change, keeping an array of snapshots, and we are setting the state tree on every change of the slider. We also printed the state tree to the screen using getStateTree() , and because the state tree object is observable, any changes were immediately reflected by our view.

Conclusion

In this tutorial you have seen some of the more advanced features of Controllerim: setStateTree(), getStateTree(), and addOnStateTreeChangeListener(). If you got confused, don’t worry. You need to play a bit with Controllerim to grasp the concept of controllers and the state tree.

Hope you enjoyed reading!

--

--