State management in Corvid

Shahar Talmi
Nov 24, 2019 · 10 min read
Corvid loves Redux & MobX

Thanks to the Wix editor, when working with Corvid you don’t need to deal with html/css when developing UI. Instead you get a full blown WYSIWYG editor where you can create the UI for your application. Then all that’s left to do is write the application logic, which is really what we want to focus on when developing applications.

Most examples you’ll see today that connect application logic to the view are similar to old style jQuery applications. You bind to event handler of UI elements and in response to those events you run some logic which updates other UI elements with the result.

jQuery style

Let’s take an example. Say you have a view with a text element (#counter) which displays a counter and two button elements (#increment & #decrement) which increment or decrement that counter.

The Corvid code which implements that logic will be something like:

This is a coding style that professional frontend developers cringe when they encounter since it reminds them the old jQuery days when we explicitly updated the UI with the effect of each interaction.

In order to understand why this is a bad pattern, let’s try to complicate the example a bit. Let’s say we also have another text element (#counter2) With it’s own increment/decrement buttons. This time we will also use the opportunity to do a small refactor. The logic implementation for this will be something like:

As you can see , in this pattern after we update some state (counter/counter2), and then we go and updated the relevant UI which should be affected by that state change. So if for example we add an additional text item calculating the sum of the two counters, we update it in all places:

Whoops! Did you see the bug? I forgot to call renderSum() also when #increment2 is clicked… Well, this is what happens when you wire UI and state manually. This only gets worse as the application gets more complicated and as behavior of the application change as you add more features.

See for example this todo list application, written in Corvid using the jQuery pattern: https://shahartalmi36.wixsite.com/corvid-jquery

We have two bugs there:

  1. If you check the checkbox next to the todo item which marks it as done, the todo description gets strikethrough decoration, which is the expected behavior. But if you check the top checkbox which marks all todo items as done, we forgot to update the to item description with the strikethrough.
  2. If you check the checkbox next to the todo item which marks it as done, the “items left” counter at the left bottom is updated with the number of remaining items. But if we delete an uncompleted todo item using the delete button, we forgot to update the “items left” counter.

Those are annoying and hard to catch bugs. Every jQuery application was full of them and it made maintaining the code base of jQuery applications complete hell.

You can also play with the real app. Just open the corvid-jquery example in the Wix editor (turn on dev mode from the top menu in order to see the code).

And then came data binding

Data binding is a pretty neat concept where you no longer need to explicitly update the UI when the state changes. Instead, you define what piece of state goes in each UI element using some framework. Then, when you update the state in that framework, it automatically updates the UI with the relevant changes. Since the framework knows what UI element needs what piece of state, it can make sure to update only the UI elements that care about the part of state that was updated.

So what are those framework? Actually there’s tons of them. The first widely used such framework was actually Angular, but Angular was much more than just a data binding or state management framework. It was very coupled with many more concerns and most importantly it was coupled with how the UI is rendered, which is obviously a problem for us since we want to render the UI with Corvid.

But then something nice happened. React came out and put on its flag to only be opinionated about how UI is rendered, the rest was open for extensibility. Soon, a new generation of state management frameworks appeared which only gave you a method to manage your state and only needed small adapters to bind the state into the React based application view. Later versions of Angular also allowed to easily use such state management instead of the built-in state management that came with Angular.

This was great since essentially you can write almost all of your application logic without really caring what framework you would use for rendering the UI. It means that if you used such state management framework, you could pretty easily move your application logic from React to Angular or even some other future framework that might come out (like Corvid!) and the only thing that would change is the wiring of the state to the UI.

In this article we will look into my two favorite state management frameworks (Redux & MobX) and see how you can easily connect them to a Corvid application. This also means you can easily take any Redux or MobX based application and migrate it from React to Corvid!

In order to use Redux and MobX you’ll need to install those external libraries and also an additional library called corvid-redux in your Corvid application.

Redux

In Redux the main concept is that your state is managed by a reducer and updated by dispatching actions. Basically that means that every time you want to update the state you dispatch an action with the needed update, then the reducer (which is basically just a function with two arguments) is called with the current state and the action and is supposed to return the new state.

It sounds complicated, but it is really simple. Let’s see the counter example:

So all that changed here is that instead of incrementing or decrementing the counter ourselves when the buttons are clicked, we dispatch an INCREMENT or DECREMENT action. Redux will then call the reducer with the current state and the dispatched action and the reducer will return the new state with the incremented or decremented counter according to what action was processed.

The most interesting line to focus on now is:

store.subscribe(() =>
$w('#counter').text = `${store.getState().counter}`
);

What this basically does is that we subscribe for changes on the store and then when the state update, Redux will call our callback and we will have the opportunity to update the view with the new state.

This is nice, but it is not granular enough. Currently we have just one counter, but in the example before we had two counters and a sum which will look more like this:

Basically in the subscribe callback we update all of the UI elements with the new state. Which is not very efficient, since there is no reason to update the first counter if the second counter is the one that was incremented. For this we have to add the corvid-redux binding which ensures just that by making the data bindings more declarative:

What we basically say here is that we bind the text property of the respective UI element with the value of the counter/sum. Now instead of updating all of the UI on state update, the UI will be updated only if the bound value is changed. In order to make this work all we needed to do is use the createConnect method from corvid-redux which gave us two helpers: pageConnect, which is basically a small wrapper on top of $w.onReady which we used up until now, and connect which we use to bind to element properties.

Pretty simple, right? Let’s complicate it a bit. Let’s make a simplified todo list. Now we have a text input (#input) and an add button (#add) and we have a repeater (#repeater) which will display the items that we added. For each item in the repeater we’ll have a text element displaying the description (#description) and a delete button to remove it from the list (#remove).

This will look something like this:

Now, this is interesting. What we do here is that we bind an array to the data property of the repeater element (#repeater). As you can see, the initial state of that array are three todo items, each item has a _id property and a description property. The _id is a unique identifier mandatory for any repeater item as described in Corvid docs. The second thing interesting here is repeaterConnect which we use in order to bind elements inside the repeater to our state.

repeaterConnect gets two parameters: the repeater element we want to bind into and a callback. This callback is called for each new item in the repeater (including the initial three items of course) in order to allow it to bind the internal elements of this repeater item to the state. You don’t need to worry about unbinding when items are removed since corvid-redux takes care of that automatically.

As you can see, the callback simply receives the $item selector function, which you can use to select internal elements of the item and the _id which you can use to access the appropriate item in the state. Binding inside repeaterConnect is done using connect exactly as you would have performed binding outside a repeater.

Note that as always with Redux, we must remember that state is immutable, which means we are not allowed to save references to an item inside the array, since that reference will never contain later changes to the state. Instead, we must find the correct item in the array inside the connect callback like so:

connect(state => ({
text: state.find(todo => todo._id === _id).description
}))($item('#description'));

Since usually we will have multiple such connect calls inside a repeaterConnect probably we can do a small refactor and extract the find part to a function and then the connect part will be somewhat shorter:

const find = state => state.find(todo => todo._id === _id); connect(state => ({
text: find(state).description
}))($item('#description'));

In general, as we learned here, keeping all logic of the state far away from the binding code is always good practice since it allows you to replace view frameworks in the future with small changes to your logic code. I highly recommend reading Redux docs about derived data in order to learn more patterns regarding how to extract data from state.

This is actually all we need to know in order to use Redux in Corvid. There are many interesting things to learn about Redux, such as how to add middleware to your store and how to handle asynchronous operations, but the nice thing is that it is all just Redux and isn’t specific to the view technology you use, which in our case just happens to be Corvid. You can read all about those in the Redux docs about advanced topics and try to apply them in your Corvid application.

For a live example of a more complicated todo list open the corvid-redux example in the Wix editor (turn on dev mode from the top menu in order to see the code).

MobX

To be honest, I’m not a big fan of Redux. The boilerplate some patterns introduce are sometimes just too much and in general I think that for most use cases the disadvantages of immutability are bigger than its advantages. That’s not to say that I never use Redux, it can sometime be very helpful. Let’s examine an alternative state management solution, which I mostly prefer to use — MobX.

In MobX the main concept is that you bind to some derived state and MobX knows to identify automatically your state dependencies and automatically creates an observable for it and runs your binding again only when the state that you depend on changes. Let’s see MobX in action with our two counters and sum example:

That’s it. The cool thing about MobX is that it will run the autorun methods only when the state that they use has changed. So when we update counter the second autorun doesn’t run, and when we update counter2 the first autorun doesn’t run. The third autorun will run in both cases since it depends on sum which depends both on counter and counter2.

Note that instead of having that sum getter function in the observable, we could have done something like:

autorun(() => $w('#sum').text = `${state.counter+state.counter2}`);

This would have worked just the same and we wouldn’t have needed the getter. However, as I mentioned before — it is wise to separate the logic from the view, because then you can easily reuse your state when changing the view technology. I recommend to always do all calculations in the observable state using getters and leave the view as dumb as possible.

Let’s try to see how MobX holds when we try the simplified todo list example. Just to remind you: We have a text input (#input) and an add button (#add) and we have a repeater (#repeater) which will display the items that we added. For each item in the repeater we’ll have a text element displaying the description (#description) and a delete button to remove it from the list (#remove).

All we needed to do, just like with Redux, is to bind an array to the data property of the repeater element (#repeater). Now onItemReady will be called for any new item and onItemRemoved for any removed item. Inside onItemReady we do all of the binding necessary using the $item selector.

Notice one special thing, which is very important to understand: the returned values from all of the calls to autorun must be saved as an array in the destroyers map, which is later invoked for each return value when the item is removed. The reason for this is that the return value of an autorun call is actually an unsubscribe method for that specific autorun. In corvid-redux, this is done automatically for us, but in MobX we must call those unsubscribe methods when the item is removed.

One place where MobX is much more comfortable is when you want to do some side effect in the binding of some value, for example hiding and showing an element. Let’s say you have some boolean state that says if some element should be visible or not. Since visibility in Corvid is controlled through show/hide functions, it is a bit tricky in Redux. In MobX you would simply do:

autorun(() => state.shouldShow ?
$w('#element').show() : $w('#element').hide());

Where as in Redux, corvid-redux needs to supply you with a magical visible property which behind the scene calls show/hide:

connect(state => ({visible: state.shouldShow}))($w('#element'));

For a live example of a more complicated todo list open the corvid-mobx example in the Wix editor (turn on dev mode from the top menu in order to see the code).

Shahar Talmi

Written by

Head of Frontend at @WixEng . Aspiring slap bet commissioner.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade