Keeping your Vuex relationships well watered

Keeping a consistent state in your Frontend application is not the easiest thing we have to deal with these days, but fortunately we have libraries such as Vuex to help us out. Using a redux style store is almost a prerequisite these days and many projects choose to do so, but there are still problems that we encounter.

In this post Id like to write about a pretty common one that you might have encountered when using this architecture, and the solution I have come up with to fix it.

The Problem

In an application I am working on I have an Entity called Hosts. It is stored in the Store as an array of objects

[{name: "", ip: "", port: ""}, ...]

and has a few mutation functions for changing the values in a basic CRUD style.

There is another Entity called Network in the store as well:

networks: {
    name: "",
    subnet: "",
    hosts: [{name:"", ip: "", port: ""}, ...]
}

Pretty straight forward and works nicely when first implementing it. The title of this post indicates the problem though, the relation between the Host and Network entity. More specifically, how do I keep the host entries in the networks up to date when I change them in some other place of my application?

Solution

My solution for now is to only store one piece of uniquely identifiable information about each host in the network, when injecting it into the store.

The store action for this looks as follows:

actions: {
    add: ({ commit }, network) => {
        const networkToSave = Object.assign({}, network, {
          hosts: network.hosts.map(host => host.name)
        });
        commit("add", networkToSave);
    },
}

As soon as the request to store a new network enters into the function I create a new object, inject all the values from the network into it and then set the hosts attribute to an array of strings, so that only the name, instead of the whole host object, is stored.

Once this new Object is created it gets passed to the mutation to actually save it into the store.

Quite easy to set up and already loads of unnecessary data does not have to be saved. Extra useful when you want to save your state to a Backend as well, as the request will be smaller.

But how is it possible to get all the correct data back out of the store when the network, with all its hosts, is needed?

Getters

Instead of accessing the state directly, which I usually try to avoid, the solution here is to use a getter function:

getters: {
  networks: (state, getters, rootState, rootGetters) => {
    const hosts = rootGetters["hosts/hosts"];

    return state.networks.map(network =>
      Object.assign({}, network, {
        hosts: network.hosts.map(host =>
          hosts.find(hostFromStore => hostFromStore.name === host)
        )
      })
    );
  };
}

When networks are supposed to be delivered from the store, the first thing that is done here is to get all available hosts in the platform and store them in a variable (rootGetters is used here because this happens inside a namedspaced module). Next the hostnames stored on each network are replaced with the corresponding full objects from the just created variable.

All of this works since the name attribute on the hosts is unique. If no unique attribute such as name exists, an ID can be added as a new attribute.

Now I have updated network entities whereever they are used in the application, and they will be reactive as well. Should any host change its configuration in the background, it will be immediately reflected in the UI.

End

A quick and easy solution to keep your entity relationships under control in a Vue frontend, but it should work on any redux style store. All in all its just a form of Hydration, but a useful one for sure when pointers arent an option.

  • Learned something?
  • Want more code explanation in a future post?
  • Did I make a mistake?
  • Dont like my style?

Let me know in the comments and Ill make sure to take in your feedback.

Cover logo by unsplash-logoGary Butterfield
Back to overview