Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Vuex rendering data that is fetched from REST API

Tags:

vue.js

vuex

For such component

<template>   <div>     <router-link :to="{name:'section', params: { sectionId: firstSectionId }}">Start</router-link>   </div> </template>      <script lang="ts">   import { mapActions } from "vuex"        export default {     mounted() {       this.getSectionId()     },     computed: {       firstSectionId() {         return this.$store.state.firstSectionId       }     },     methods: mapActions(["getSectionId"])   } </script> 

Store:

const store: any = new Vuex.Store({     state: {         firstSectionId: null     },     // actions,     // mutations }) 

I have a web request in the getSectionId action and it asynchronously fetches data and calls a mutation that will fill firstSectionId in state. During the initial rendering firstSectionId is null and I get the warning that a required parameter is missing during rendering of router-link.

It is not a problem here to add v-if="firstSectionId". But in general what is the approach for fetching data from a server to be displayed? Currently all my components are checking if there is data present in the store before rendering, is it normal or is there a better way to wait for data to be loaded before rendering it?

like image 277
Sly Avatar asked Jan 12 '17 09:01

Sly


People also ask

Where does Vuex store its data?

At the center of every Vuex application is the store. A "store" is basically a container that holds your application state. There are two things that make a Vuex store different from a plain global object: Vuex stores are reactive.

Does Vuex Dispatch return a Promise?

It's possible for a store. dispatch to trigger multiple action handlers in different modules. In such a case the returned value will be a Promise that resolves when all triggered handlers have been resolved.

What is the use of mapState in Vuex?

Mapping State Vuex provides a helper function called mapState to solve this problem. It is used for mapping state properties in the store to computed properties in our components. The mapState helper function returns an object which contains the state in the store.


2 Answers

One approach for asynchronously fetching data is to use promise in vuex store actions.

Vue.http.get(API_URL)   .then((response) => {      //use response object         })   .catch((error) => {     console.log(error.statusText)   }); 

To demonstrate that I make request to this route. You can see how response should looks like. Let's save response object in state.users array.

store.js

const store = new Vuex.Store({   state: {     users: []   },     mutations: {     FETCH_USERS(state, users) {       state.users = users     }   },   actions: {     fetchUsers({ commit }, { self }) {                 Vue.http.get("https://jsonplaceholder.typicode.com/users")         .then((response) => {           commit("FETCH_USERS", response.body);           self.filterUsers();            })         .catch((error) => {           console.log(error.statusText)         });     }   } })      export default store 

You noticed that there is self.filteruser() method after commit. That is crucial moment. Before that we are committing a mutation, which is synchronous operation and we are sure that we will have our response in store.state that can be used in filterUsers() method (don't forget to pass self parm)

Users.vue

import store from "../store/store"  export default {   name: 'users',   created() {     this.$store.dispatch("fetchUsers", { self: this })          },   methods:{     filterUsers() {       //do something with users       console.log("Users--->",this.$store.state.users)            }   } } 

Better ways (ES6 & ES7)

ES6 Promises for asynchronous programming

//User.vue created() {   this.$store.dispatch("fetchUser").then(() => {     console.log("This would be printed after dispatch!!")   }) }  //store.js actions: {   fetchUser({ commit }) {     return new Promise((resolve, reject) => {       Vue.http.get("https://jsonplaceholder.typicode.com/users")         .then((response) => {           commit("FETCH_USERS", response.body);           resolve();          })          .catch((error) => {            console.log(error.statusText);          });     });   } } 

ES7: async/await

To get away from callback hell, and to improve asynchronous programming use async function, and you can await on a promise. Code looks much easier to follow (like it is synchronous), but code isn't readable for browsers so you'll need Babel transpiler to run it.

actions: {   async actionA ({ commit }) {     commit('gotData', await getData())   },   async actionB ({ dispatch, commit }) {     await dispatch('actionA') // wait for actionA to finish     commit('gotOtherData', await getOtherData())   } } 
like image 107
t_dom93 Avatar answered Oct 13 '22 00:10

t_dom93


In my experience, you can skip a few checks if you preset the state with an empty value of the same type as the expected result (if you know what to expect, of course), e.g. if you have an array of items, start with [] instead of null as it won't break v-for directives, .length checks and similar data access attempts.

But generally, adding v-if is a very normal thing to do. There's a section about this in the vue-router documentation and checking whether properties exist or not is exactly what it suggests. Another possible solution it mentions is fetching data inside beforeRouteEnter guard, which assures you will always get to the component with your data already available.

Ultimately, both solutions are correct, and the decision between them is more of a UX/UI question.

like image 41
mzgajner Avatar answered Oct 12 '22 23:10

mzgajner