Vuetify v-select not showing items after full refresh - vuex

I have a dialog with a v-select that doesn't show values in the drop down after a full page load. It does work after a hot module reload triggered by modifying the component file.
Using vuetify:
"vuetify": "^1.5.17",
Component contains:
template code:
<v-select
:items="routes"
label="Routes"
multiple
chips
persistent-hint
hint="Send to which routes"
v-model="message.routes"
></v-select>
routes is a computed property:
routes: {
get() {
return this.$store.state.routes
}
}
The data gets downloaded in the created event:
created() {
this.downloadRoutes()
}
This maps to a store method that does an AJAX call that commits the returned list:
downloadRoutes({ commit, state }) {
return new Promise((resolve, reject) => {
commit('SET_LOADING', true)
api.get('/route').then(response => {
var routes = response.data.routes
commit('SET_ROUTES', routes)
commit('SET_LOADING', false)
resolve()
})
.catch(function(error) {
commit('SET_LOADING', false)
reject()
})
})
},
AJAX response is just an array of routes:
{"routes":["Route1","Route2","RouteXX"]}
This I have shown by doing a console.log of the response and the state in the computed routes property.
What am I missing?

It sounds like your problem lies somewhere inside the vue instance lifecycle, i.e. you might call this.downloadRoutes() inside the wrong hook. Do your problem still occure if you try with a hardcoded array of Routes?

Found the problem.
My store is initialised via an initialState function.
export default new Vuex.Store({
state: initialState,
getters: {
......
The initial state function declares all the top level collections used in state.
function initialState() {
return {
fullName: localStorage.fullName,
routes: [] // Was missing
}
Adding routes in that initial state solves the problem.
I think it's because watchers on state are not added when new attributes are added to state.
When I do a full page refresh the routes gets added to state after the select is initialized and so it is not watching for changes. During hot module reload the routes is already in the state and is so picked up by the select.

Related

Axios requestBody is out of sync with actual object getting parsed to it

I have a VUE 3 application where we are experiencing some sync issues when clicking on our toggles.
When the toggle is clicked, the app is supposed to call our API with the updated list.
THE PROBLEM:
When the toggle is clicked the data within the computed property is correct. We are then emitting that data to the parent component (data is still correct when received at the parent component).
Parent component calls the API which updates the data. But in the request body, the data is not correct, that is still the old list.
If I click on one of the toggles again the data being sent is then correct from the previous update (it is one click behind).
Here are the steps the app is going through:
A vue component emits computed property to the parent component
// CHILD COMPONENT EMITTING DATA TO PARENT
____filters.vue___
<template>
<ion-toggle
#click="changedFilter"
v-model="filter.selected"
:checked="filter.selected"
slot="end">
</ion-toggle>
</template>
setup(props, context) {
const changedFilter = () => {
console.log(props.searchFiltersArr) ------> **THIS DATA IS CORRECT**
context.emit("changedFilter", props.searchFiltersArr);
};
return {
changedFilter,
filters: props.searchFiltersArr,
};
}
Parent component receives emitted data from the child and calls the API using Axios.
___ SearchFilters.vue _____
<template>
<filters
#changed-filter="updateFilter"
:searchFiltersArr="searchParameters">
</filters>
</template>
export default defineComponent({
name: "SearchFilters",
components: { Filters },
setup() {
const store = useStore();
const searchParameters = computed(() => {
return {
searchParameters: store.state.user.settings.searchParameters,
};
});
async function updateFilter(search: Array<SearchFilter>) {
const headers = {
"Content-type": "application/json",
Authorization:
"Bearer " + "21345",
};
const requestBody = {
user: {
name: "",,
email: "email#email.com",
},
searchParameters: search,
};
console.log(requestBody); -----------> **REQUEST BODY HAS THE CORRECT VALUES FROM CHILD COMPONENT**
await apiClient.put("/settings", requestBody, {
headers,
}); -----> ** REQUEST BODY IN THE ACTUAL REQUEST IS NOT THE UPDATED CONTENT **
}
return {
updateFilter,
searchParameters: searchParameters.value.searchParameters,
};
},
});
The updated data is correct all the way until we call the service. Then the body is incorrect.
How can this be?
If i wrap the axios request inside of a set timeout like so, it works.
setTimeout(function (){
axios service }, 2000)
Let me know if you need any further information.
This is just a simplified example, as this shows the problem. In the real application we are calling a vuex action which then call the api and thereafter commits the updated state. The issue is exatcly the same though.
The problem is somewhat similar to this post - ReactJS - synchronization issue with axios.post
I found a solution to the problem.
Since vue can’t guarantee the order of events (computation should run before click event), I removed the click event and added a watcher instead

How to call bootstrap-vue modals and toasts from vuex actions?

Did anyone tried to use boostrap-vue in combination with vuex?
I'm having hard time calling modals and toast from vuex actions.
Obviously I can not use this from vuex store, therefore I can't use:
this.$bvModal.show('modalId');
I also tried calling modal like this
import Vue from 'vue';
Vue.prototype.$bvModal.show('transaction');
But console gives me following warning:
BootstrapVue warn]: '$bvModal' must be accessed from a Vue instance 'this' context
Any idea how I can call modals and toasts from vuex actions directly?
Try to call this._vm.$bvModal.show('modalId');.
Reference.
I think a better approach is to prevent to deal with the UI from the Store. So, you can add a store property and watch for changes from your components.
In the following example, I added an array toastMessages in the state property and a ADD_TOAST_MESSAGE mutation to add some toastMessage. You can then commit an ADD_TOAST_MESSAGE mutation from another mutation or from an action.
Inside your Top-level component (App.vue), you can watch for your toastMessages state property changes and display the last item that was pushed.
App.vue
<script>
export default {
name: "App",
created() {
this.$store.watch(
state => state.toastMessages,
toastMessages => {
this.$bvToast.toast(this.toastMessages.slice(-1)[0]);
}
);
}
}
</script>
store.js
export default new Vuex.Store({
state: {
toastMessages: []
},
mutations: {
ADD_TOAST_MESSAGE: (state, toastMessage) => (state.toastMessages = [...state.toastMessages, toastMessage]),
},
actions: {
myActionThatDoSomething({commit}, params) {
// Do something
commit('ADD_TOAST_MESSAGE', "Something happened");
}
}
});
found a solution here: https://github.com/vuejs/vuex/issues/1399#issuecomment-491553564
import App from './App.vue';
const myStore = new Vuex.Store({
state: {
...
},
actions: {
myAction(ctx, data) {
// here you can use this.$app to access to your vue application
this.$app.$root.$bvToast.toast("toast context", {
title: "toast!"
});
}
}
});
const app = new Vue({
el: '#my-div',
render: h => h(App),
store: myStore
});
myStore.$app = app; // <--- !!! this line adds $app to your store object
Using #eroak idea, I have implemented this same thing for vue-sweetalert2.
I have also created a store for my Sweet Alert Toaster, then I am watching a property called ticks that is updated whenever state of the toaster is updated.
I am using ticks as I only have one message in my state, and ticks just timestamp when action was triggered.
You can find whole demo here: https://github.com/Uraharadono/CallbackFromVuexAction
Try to call this._vm.$root.$bvModal.show('modalId');

dynamic importing components based on route

I'm new to VueJS and I haven't found a possibility to load components based on route. For example:
page/:pageid
page/one
page/two
I have a component Page.vue
Within that component, I watch route changes. If the route is $pageid, then import and load component $pageid.
I've read this documentation: https://v2.vuejs.org/v2/guide/components-dynamic-async.html. But that's more focussed on lazy-loading. I don't see an example for dynamic importing and loading.
Regards, Peter
According to Dynamic Route Matching of vue router, you can access the url parameters via the params property of the $route object. In your case it would be $route.params.pageid so you can use it to dynamically change the content base on the pageid parameter in the url. Also note that on url change from say in your case page/one to page/two the same component would be used, so you would have to watch the $route object change and change your content dynamically.
watch: {
'$route' (to, from) {
// react to route changes...
}
}
Vue allows you to define your component as a factory function that
asynchronously resolves your component definition.
Since import() returns a promise, so you can register your async component by using:
export default {
components: {
'Alfa': () => import('#/components/Alfa'),
'Bravo': () => import('#/components/Bravo'),
'Charlie': () => import('#/components/Charlie')
}
}
Vue will only trigger the factory function when the component needs to
be rendered and will cache the result for future re-renders.
So your component will be load only when it need to be render.
And you can use dynamic component to render it by using:
<component :is='page'/>
and
export default {
computed: {
page () {
return 'Alfa'
}
}
}
If you already using vue-router you can directly use this in routes definition. See more in document here.
const router = new VueRouter({
routes: [{
path: '/alfa',
component: () => import('#/components/Alfa')
}, {
path: '/bravo',
component: () => import('#/components/Bravo')
}, {
path: '/charlie',
component: () => import('#/components/Charlie')
}]
})
As you can see this is dynamic importing but static registration (you have to provide the path to component.) which fit mostly in many situations. But if you want to use dynamic registration, you can return component directly instead of name see document here.
export default {
computed: {
page () {
return () => import('#/components/Alfa')
}
}
}

Make Vue template wait for global object returned by AJAX call

I'm trying to wait for certain strings in a sort of dictionary containing all the text for buttons, sections, labels etc.
I start out by sending a list of default strings to a controller that registers all the strings with my CMS in case those specific values do not already exist. After that I return a new object containing my "dictionaries", but with the correct values for the current language.
I run the call with an event listener that triggers a dispatch() on window.onload, and then add the data to a Vuex module state. I then add it to a computed prop.
computed: {
cartDictionary() {
return this.$store.state.dictionaries.myDictionaries['cart']
}
}
So now here's the problem: In my template i try to get the values from the cartDictionaryprop, which is an array.
<h2 class="checkout-section__header" v-html="cartDictionary['Cart.Heading']"></h2>
But when the component renders, the prop doesn't yet have a value since it's waiting for the AJAX call to finish. And so of course I get a cannot read property of undefined error.
Any ideas on how to work around this? I would like to have the dictionaries accessible through a global object instead of passing everything down through props since it's built using atomic design and it would be insanely tedious.
EDIT:
Adding more code for clarification.
My module:
const dictionaryModule = {
namespaced: true,
state: {
dictionaries: []
},
mutations: {
setDictionaries (state, payload) {
state.dictionaries = payload
}
},
actions: {
getDictionaries ({commit}) {
return new Promise((resolve, reject) => {
Dictionaries.init().then(response => {
commit('setDictionaries', response)
resolve(response)
})
})
}
}
}
My Store:
const store = new Vuex.Store({
modules: {
cart: cartModule,
search: searchModule,
checkout: checkoutModule,
filter: filterModule,
product: productModule,
dictionaries: dictionaryModule
}
})
window.addEventListener('load', function () {
store.dispatch('dictionaries/getDictionaries')
})
I think you can watch cartDictionary and set another data variable.
like this
<h2 class="checkout-section__header" v-html="cartHeading"></h2>
data () {
return {
cartHeading: ''
}
},
watch: {
'cartDictionary': function (after, before) {
if (after) {
this.cartHeading = after
}
}
}
Because this.$store.state.dictionaries.myDictionarie is undefined at the the begining, vuejs can't map myDictionarie['core']. That's why your code is not working.
You can do this also
state: {
dictionaries: {
myDictionaries: {}
}
}
and set the dictionaries key values during resolve.
I also would have liked to see some more of your code, but as i can't comment your questions (you need rep > 50), here it goes...
I have two general suggestions:
Did you setup your action correctly? Mutations are always synchronous while actions allow for asynchronous operations. So, if you http client returns a promise (axios does, for example), you should await the result in your action before calling the respective mutation. See this chapter in the official vuex-docs: https://vuex.vuejs.org/guide/actions.html
You shouldn't be using something like window.onload but use the hooks provided by Vue.js instead. Check this: https://v2.vuejs.org/v2/guide/instance.html#Lifecycle-Diagram
EDIT: As a third suggestion: Check, whether action and mutation are called properly. If they are handled in their own module, you have to register the module to the state.

How persist some state between routes with vue & vue-router

I have a navigation flow consisting in
SearchPage -> ...Others or SearchPage -> ...Others or SearchPage ->
and wanna persist what was the search string when navigate back.
<template id="ListCustomersPage">
<q-layout>
<q-layout-header>
<toolbar :title="title" :action="doCreate" label="New"></toolbar>
<q-search inverted placeholder="Type Name, Code, Nit, Phone Number or Email" float-label="Search" v-model="search" />
<q-btn icon="search" color="secondary" #click="doSearch" />
</q-layout-header>
</q-layout>
</template>
Now, the problem is how correlate the stack of the queries and the one of the routers, when the fact the user can navigate elsewhere.
P.D All is in a single page. If possible to persist the screen without refresh them (but only for the search pages until popped back) will be better.
Option 1: Navigation Guards
You can use a so called Navigation Guard that allows you to add global actions before, after and on route updates. You can also add it directly to your component by using the In-component Guards, which will allow you to persist the content of the search data.
const VueFoo = {
// I guess your search attribute is in your data
data() {
return {
search: ''
}
},
mounted() {
// retrieve your information from your persistance layer
},
beforeRouteLeave (to, from, next) {
// called when the route that renders this component is about to
// be navigated away from.
// has access to `this` component instance.
// persist this.search in localstorage or wherever you like it to be stored
}
}
Option 2: Using a (Vuex) Store
If you're able to add a Vuex Store or any Store alike, I would highly recommend to do so. Since you tagged quasar I want to link to the Vuex Store Documentation provided there. You can basically outsource your search property and let the Store persist it for you across your application.
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
search_term: ''
},
mutations: {
SET_SEARCH_TERM (state, payload) {
state.search_term = payload.search_term
}
},
actions: {
SEARCH ({ commit, state }, payload) {
commit('SET_SEARCH_TERM', payload.search_term)
// your api call to search which can also be stored in the state
}
}
})
export default store
In your component where you want to persist your search query using a mutation (not bound to an action):
store.commit('SET_SEARCH_TERM', {
search_term: this.search // your local search query
})
In your code where you trigger the search ACTION if you want to persist during every search
store.dispatch('SEARCH', {
search_term: this.search
})
Accessing the property search_term or however you want to call it can be done using a computed property. You can also bind the state and mutations directly without the need for Navigation guards at all:
// your Vue component
computed: {
search: {
get () {
return this.$store.state.search_term
},
set (val) {
this.$store.commit('SET_SEARCH_TERM', { search_term: val })
}
}
}
Make sure to learn about the basic concept before using: https://vuex.vuejs.org/