Navigating through asynchronous data retrieval in Vuex and Vue

I currently have a basic app utilizing a standard stack :

  • A backend server (Rails)
  • A frontend app (Vue)
  • A database (PG)

In this setup, the Vue app retrieves data from the backend by using an action within the Vuex store library, like so:

// store/store.js
import Vue from 'vue';
import Vuex from 'vuex';
import * as MutationTypes from '@/store/mutation-types';

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    investment: {},
  },
  mutations: {
    [MutationTypes.SET_INVESTMENT_SHOW](state, investment) {
      state.investment = investment;
    },
  },
  actions: {
    fetchInvestment({ commit }, id) {
      InvestmentsApi.get(id).then((response) => {
        commit(MutationTypes.SET_INVESTMENT_SHOW, response.data);
      });
    },
  },
  getters: {
    participation: state =>
      state.investment.included[0],
  },
});

To trigger this action, I call it in the created lifecycle hook of my component:

// components/Investment.vue

import { mapActions, mapGetters } from 'vuex';
export default {
  name: 'Investment',
  computed: {
    ...mapState(['investment']),
    ...mapGetters(['participation']),
  },
  created() {
    this.fetchData(this.$route.params.id);
  },
  methods: mapActions({
    fetchData: 'fetchInvestment',
  }),
};

However, there seems to be an issue with the code above. Specifically, when I utilize the computed value 'participation' in my template like this:

<BaseTitleGroup
  :subtitle="participation.attributes.name"
  title="Investissements"
/>

This results in an error from the getter method due to trying to access '0' property of undefined at the time of rendering.

I've considered various solutions to tackle this problem and would appreciate insight on the best approach or any better alternatives:

  1. The first solution involves adding a v-if attribute in the template to prevent rendering until the data is available.
    • Cons : Delayed rendering
    • Cons : Needing to implement this for every async data-reliant component in my app
  2. Display initial fake data, such as "Loading...", before actual data loads.
    • Cons : User may perceive glitch when content transitions from loading text to real data
    • Cons : Tedious task to maintain empty store values as app grows
  3. Modify the getter to return initial empty data instead of directly retrieving data from the store.
    • Cons : Complication in getters
    • Cons : How to handle data not needing a getter?
  4. Any other suggestions?

I am seeking the optimum solution for handling this pattern effectively. Any advice or recommendations are greatly appreciated! Thank you for taking the time to read through this. By the way, here's a potato for your efforts! (Not exactly 9gag ;) )

Answer №1

When working with Angular, you have access to the Elvis (safe navigation) operator, a convenient tool for managing reactive data that may arrive at some point in time.

Imagine if this operator were available in the Vue template compiler - your template code would be simplified like so:

<BaseTitleGroup
  :subtitle="participation?.attributes?.name"
  title="Investissements"
/>

However, according to Evan You, using this approach "sounds like a code smell." He emphasizes the importance of having a predictable model/state.

Your model/state should be as predictable as possible.

This can be interpreted as meaning that your template has more knowledge about the data structure than your store.

template

"participation.attributes.name"

translates to:

state.investment.included[0].attributes.name

store

state: {
  investment: {},
},

Instead of relying solely on the template, it might be better to enhance the getter function to serve the component and its template.

getters: {
  participation_name: state => {
    return 
      (state.investment.included 
       && state.investment.included.length
       && state.investment[0]
       && state.investment[0].attributes
       && state.investment[0].attributes.name)
      || null;
},

<BaseTitleGroup
  :subtitle="participation_name"
  title="Investissements"
/>

If you still desire the elvis functionality, consider implementing it through a mixin.

var myMixin = {
  computed: {
    elvis: {
      get: function() {
        return (known, unknown) => {
          // use regex split to handle both properties and array indexing
          const paths = unknown.split(/[\.(\[.+\])]+/); 
          let ref = known
          paths.forEach(path => { if (ref) ref = ref[path] });
          return ref;
        }
      }
    },
  }
}

export default {
  name: 'Investment',
  ...
  mixins: [myMixin],
  computed: {
    ...mapState(['investment']),
    participation_name() {
      return this.elvis(this.investment, 'included[0].attributes.name')
    }
  },
  ...
};

Answer №2

In my opinion, there is no definitive best solution when it comes to choosing between different methods in programming. It's important to pick one approach and consistently use it instead of mixing various techniques.

Specifically, the v-if directive can be a more suitable option if you need to render data from a nested property. Using

v-if="object.some.nested.property v-for="el in object.some.nested.property"
may work, but initializing object = {} beforehand will lead to an error due to accessing an undefined property.

While I wouldn't recommend inserting fake data like in your example, leveraging ES6 Classes to define default objects and using them as defaults can resolve the issue of predefining variables. This approach also enhances code transparency and comprehension.

Regarding the third alternative - providing empty data to getters can be simplified by adjusting the getter function:

getters: {
    participation: state =>
      state.investment.included[0] || new DefaultParticipationObject() // I don't know what's in included array
  },

This code snippet utilizes state.investment.included[0] if it exists, or a default object otherwise.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Is it possible to incorporate a VueJS .vue component into an HTML page without the need for a WebPack build process?

I'm currently working on a page for an older server-rendered project. For one specific page, I want to incorporate some "dynamic" elements. I decided to import the vuejs CDN script and create an inline Vue object: var v = new Vue(... However, there ...

I'm curious why Axios is calling my API over HTTP instead of HTTPS when I have specified an HTTPS

Whenever I call the API, I encounter this error: https://i.sstatic.net/iFRP9.png Here is my function: https://i.sstatic.net/KJpyz.png All of my routes are functioning properly except for this one and I can't figure out why. ...

Tips for displaying a message in a concealed field with jQuery

I have an input field that saves values for checking a specific condition. When the condition is false, I want to display a message. If the field is hidden, the message will not be displayed. HTML <input type="hidden" id="year1_req_fund_hidden" name= ...

Instructions for launching an Android app from a website

Is it possible to invoke my app from HTML? For instance, I am able to successfully call a webpage from my app using this code. android code: startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse( "myDomain.com"))); ...

Creating adaptable background images with fullpage.js

I'm currently utilizing fullpage.js for parallax scrolling. I'm wondering if there's a way to make the background images adjust responsively when I resize my window. Check out the documentation here: https://github.com/alvarotrigo/fullPage. ...

My website keeps crashing because the "wheel" event is being constantly triggered

import React, { useEffect, useState } from "react"; import "./Skill.css"; import { Fade } from "react-reveal"; function Skill({ name, color }) { const [style, setStyle] = useState({ borderBottom: `4px solid ${color}` }); ...

How can I extract information from Firebase and set it in the this.props of a React component?

Hey there, I'm having some trouble with my Firebase setup. I need to transfer data from snapshot.val() into this.props.device, but when I try to do so within the this.firebaseRef.on() function, 'this' is showing up as 'null'. Any s ...

Using Google App Script's pageToken to store attachments on Google Drive

Essentially, my goal is to save all attachments from received emails to a specific folder in Google Drive (mostly .PDF files). However, I've encountered a limitation with the search function which only allows me to retrieve up to 500 attached files. I ...

Obtain asynchronous result from updating state in React

I am trying to achieve the following: myFunction = () => { this.setState( state => { const originalBar = state.bar; return { foo: "bar" }; }, () => ({ originalBar, newBar: state.foo }) //return this object ...

Having two ng-click events and two distinct classes in an ng-repeat based on the selected option

There are two buttons in my code that should remove a div within an ng-repeat loop. Depending on which button is clicked, a custom CSS class should be added to the effect. The CSS changes based on the option selected. When I click on a button, either &apo ...

When selecting the "Open Link in New Tab" option in Chrome, the Angular app's routing will automatically redirect to the login page

I am facing a peculiar issue in my Angular 2 application that I need help troubleshooting. Currently, the routing within my app functions as intended when I click on links to navigate between different components. Here is an example of how the routing path ...

Displaying 'hours and minutes' functions properly in all web browsers except for Safari. Utilizing JavaScript Date object

I am retrieving a date from an object within a loop in the following format: 2018-08-06 20:45:00 My objective is to only display "20:45" (always with two digits for minutes) in the client's timezone. To achieve this, I have implemented the below meth ...

Struggling to spot my error (codewars, javascript, level 8)

Can You Translate?! After receiving a message on WhatsApp from an unfamiliar number, you wonder if it's from the person with a foreign accent you met last night. Your task is to write a simple function that checks for various translations of the word ...

Icon in the footer of Bootstrap

This is the foundation of most of my web projects that I enjoy creating. I am interested in incorporating image links instead of relying on local files. Any assistance would be greatly appreciated. <!DOCTYPE html> <html lang="en"&g ...

Utilizing a PHP variable in an external JavaScript file

I have a PHP variable that I need to access in my JavaScript code. What is the best way to transfer the value from PHP to JavaScript? My PHP file contains HTML code similar to the following: <!doctype html> <html> <head> //some imp ...

Unexpected error arises in Typescript despite code functioning properly

As part of a practice project where I'm focusing on using webpack, ES6, npm and Typescript implementation, I have successfully set up loaders for 'awesome-typescript-loader' and 'babel-loader', which are bundling the code properly. ...

Is it possible to convert the useMemo function into a useReducer hook implementation?

While I've figured out how to switch from using useState to useReducer, I'm now curious if there's a similar approach for transitioning from useMemo? I've been troubleshooting some code that's causing unnecessary renders because o ...

The mystery of the unassigned value in $(this).data(value) when using the jQuery click() method

In my Rails 5 application, I am working on creating a dynamic menu that will guide users to different parts of the site based on their location. The idea is that when they click on a specific menu item from the home page, a modal will appear allowing them ...

Using JavaScript to assign function arguments based on arbitrary object values

I am facing a challenge with a collection of arbitrary functions and a method that takes a function name along with an object or array of parameters to call the respective function. The issue arises from the varying number of inputs in these functions, som ...

Transform a text file with data stored in key-value format into JSON format

Imagine having a text file with data such as: [ key = 1542633482511430199, value=>>>BasicData:isConfirmAndOrder=0,brmRequestId=BR-2018-0000124,requestType=batch,projectName=Automation_Product_By_Admin,projectId=PRJ-2018-0000477,department=Global ...