Note: This post was automatically migrated from Markdown to Typst using AI. Read more about the migration process here. Contact me if something in this post is off and bothering you.

Keeping your Vuex relationships well watered

Figure 1: A yellow neon sign spelling “STAY HYDRATED” mounted on a brick wall.

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

#link("https://vuex.vuejs.org/guide/mutations.html")[{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.

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

Cover logo by

<a style="background-color:black;color:white;text-decoration:none;padding:4px 6px;font-family:-apple-system, BlinkMacSystemFont, &quot;San Francisco&quot;, &quot;Helvetica Neue&quot;, Helvetica, Ubuntu, Roboto, Noto, &quot;Segoe UI&quot;, Arial, sans-serif;font-size:12px;font-weight:bold;line-height:1.2;display:inline-block;border-radius:3px" href="https://unsplash.com/@garybpt?utm_medium=referral&amp;utm_campaign=photographer-credit&amp;utm_content=creditBadge" target="_blank" rel="noopener noreferrer" title="Download free do whatever you want high-resolution photos from Gary Butterfield"><span style="display:inline-block;padding:2px 3px"><svg xmlns="http://www.w3.org/2000/svg" style="height:12px;width:auto;position:relative;vertical-align:middle;top:-2px;fill:white" viewBox="0 0 32 32"><title>unsplash-logo</title><path d="M10 9V0h12v9H10zm12 5h10v18H0V14h10v9h12v-9z"></path></svg></span><span style="display:inline-block;padding:2px 3px">Gary Butterfield</span></a>

</sup>