Today I Learned

TypeScripts Writable Computed Refs

Writable Computed Refs can be of real help when using Vue's Composition API along with TypeScript. Let's see what they are and how we can use them by getting through an example.

Problem description: let's assume we have to display a dialog with all our online users, if there are any. At the same time, we have the option to disable dialogs on our website, for some reason. In order achieve our goals, we are using an UI library like PrimeVue, Ant or Vuetify. These assist us with a reusable Dialog component that is visible when a boolean allows it to do so, something like this:


The problem that we encounter is that of declaring our isDialogVisible reactive variable. What are the problems you may think.

  1. It should both take into consideration if we have something to display in our list (if we have users in our store - using a getter for that) AND if we are allowed to display dialogs on our website. So it cannot be a simple reactive reference.

  2. You may think: alright, it's not really a problem, that's what computed properties are for. They are reactive and build up a value based on one or more raw variables. So this should solve the problem:

const isModalVisible = computed(() => useStore().getters.getOnlineUsers.length > 0 && areModalsAllowed.value);

Theoretically that's right, but it does not really solve our specific problem, because our dialog must also be closed in 3 scenarios (that means our isDialogVisible variable should be set to false): a. when dialogs are disabled on the website b. when we click on of the dialog's close button c. when the getter returns an empty list of users

The problem is that computed properties are not overridable. Depending on how you do it you could get one of the following warnings that cause unexpected behaviour and make the application not respond to the users commands of closing the dialog:

[Vue warn]: Computed property was assigned to but it has no setter.
[Vue warn]: Write operation failed: computed value is readonly.

Of course, it's a bad idea to empty the users list in the store and make the dialogs disabled on the website when you click on the close button, or to disable the dialogs on the website when there are no users in the store, or... you got the idea.

Here's where our Writable Computed Refs come into play and help us gracefully solve our problem:

const isDialogVisible: WritableComputedRef<boolean> = computed({
      get: (): boolean => useStore().getters.getOnlineUsers.length > 0 && areModalsAllowed.value,
      set: (newValue: boolean): void => { areModalsAllowed.value = newValue }

Explanation: We can specifically state the generic WritableComputedRef type and construct our isDialogVisible boolean by providing an object with specific get and set methods to computed().
Our set method will be automatically used by the Dialog component when having to close the dialog due to the "Disable dialogs" button's click event. But when one checks for its value, our provided get method will also take the getters value into consideration.

We can understand this as surrounding(proxying) our computed property with custom get and set methods while it stays reactive.