How would you achieve Latency Compensation (concept from Meteor) through Phoenix + Vue.js?

An Introduction To Latency Compensation

Do the prefetching yourself? As for fail-able actions, this approach seems dishonest to me, I don’t want to fool my users into thinking all their “posts” succeed including those that fail.

By what I understand of what meteor is doing is basically inserting the structure as if it was valid in the “front-end” and then conciliating that with the back-end response. It allows you to do that automatically because of the way meteor is wired, but if you want to conciliate records, basically you could do something similar to this, you would insert whatever representation of the record you have into whatever state property needs it (perhaps flagging it, say an object property is_mock with an arbitrary identifier, pass this identifier to the backend and include it on the response, then on the front-end callback from the actual server call, reconcile them, by the identifier. You need to write a bit more boiler plate on your calls but it’s pretty straightforward to do, assuming you’re using Vuex and your components draw their data from the store, you’re probably already using mutations/actions to manipulate what gets to the front-end.

I personally prefer to do something like the following pattern I started to implement in Vuejs using Vuex (and I found it to be very appropriate when using Vue as the front-end, perhaps it’s not as useful if you’re just using some vue components since then your app is not a “single component”) is to have a store module just for UI (usually named “ui” alone), with a bunch of getters, actions and mutations. Then the actions from whatever component call out to the “ui” module to toggle the state of an element.

For instance in a component I have a button that saves a record, say its bound to save_record(), on the same component I have that action mapped:


computed: {
   ...mapGetter("ui", [ "loading" ])
}

methods: {
  ...mapActions([ "save_record" ])
}

#### On the store:

save_action({ dispatch, commit }, record) {
  dispatch("ui/toggle", {element: "save_record", toggle: true});
  dispatch("axios_call", {
    method: "POST",
    url: ".....",
    data: record
  }).then(response => {
    dispatch("ui/toggle", {element: "save_record", toggle: false});
    commit("save_record", response.data.record);
  }).catch(e => { dispatch("ui/toggle", {element: "save_record", toggle: false}); })
}

#### The UI module

const state = () = ({
  loading: {}
})

const mutations = {
  toggle(state, { element, toggle }) {
    state.loading[element] = toggle;
    state.loading = {...state.loading};
  }
}

toggle({ commit }, toggle) {
  commit("toggle", toggle);
}

In the template itself I render them conditionally based on the name I used, e.g.:

<spinner v-if="loading.save_record/>
<button v-else/>

I’ve used it so much that from now I’ll just use a component that takes the condition, and a slot, to be able to do:

<ui_may_be_loading :condition="loading.save_record"><button ...>/ui_may_be_loading>

# And the component itself would be something like:

<template>
   <div>
     <spinner v-if="condition"/>
     <slot v-else/>
   </div>
<template>

#...
props: {
  condition: {
    type: Boolean,
    default: false
  }
}

This doesn’t solve the latency issue per se but it does lower the perceived latency, I found it a very reasonable way of making the ui be responsive, removing/disabling the element (so it doesn’t allow multiple clicks) while still providing a visual clue that it’s working on getting the request done. It’s also a good option for channel based asynchronous requests, where you can leave the toggle on until you receive the message you want from the backend. You need to add a bit of boilerplate for the element names, but if you do it from the beginning it’s just fine. You can eventually even thin out manual calls to toggle, by passing the element to be toggled to your axios wrapper, on the axios response/catch toggle off automatically the same element and return a second promise that you can then .then from the original action.