Why can't I load data from vuex when I refresh site? - javascript

The data is loaded into the task array and I see in vue developer tool. When you refresh the page, the resulting array from vuex is empty
Vuex file:
export const timelineState = {
tasks: [],
};
export const timelineMutations = {
getTasks(state, value) {
state.tasks = value;
}
};
export const timelineActions = {
getByProjects({ commit }, query) {
timelineApi.getByProjects(query)
.then((respons) => {
commit("getTasks", respons.data);
})
.catch((error) => {
console.log(error);
});
},
};
Vue file:
export default {
name: "Timeline",
data() {
return {
t: []
}
}
methods: {
...mapActions("timeline", [
"getByProjects"
]),
},
computed: {
...mapState("timeline", [
"tasks",
]),
},
created() {
let query = {
dateFrom: this.dateFrom,
dateTo: this.dateTo,
};
this.getByProjects(query);
this.t = this.tasks
},
}
When I navigate to the page it loads the data but if I refresh it it doesn't. The data would be needed because it needs to be sorted. I found a way to save it to local storage but I don't want to store the data. i read vuex-persistedstate but i don't know if it's a good solution because it isn't maintained

Vue is a SPA framework and SPAs doesnot support page refresh.
I recommend you to use vuex-persistedstate npm package to prevent daa loss on page refresh.
https://www.npmjs.com/package/vuex-persistedstate
Eventhough they have stopped the supprt, this still supprts the latest version of Vue JS and VueX

Related

How to store nuxtjs dynamically generated routes in vuex store

I'm trying to leverage nuxtjs SSG capabilities by creating a static web site where the pages content and navigation are fetched from an API.
I already found my way around on how to dynamically generate the routes by defining a module where I use the generate:before hook to fetch the pages content and routes. When creating the routes I store the page content as the route payload. The following code does just that and works as intended.
modules/dynamicRoutesGenerator.js
const generator = function () {
//Before hook to generate our custom routes
this.nuxt.hook('generate:before', async (generator, generatorOptions) => {
generator.generateRoutes(await generateDynamicRoutes())
})
}
let generateDynamicRoutes = async function() {
//...
return routes
}
export default generator
Now the problem I'm facing is that I have some navigation components that need the generated routes and I was thinking to store them into the vuex store.
I tried the generate:done hook but I don't know how to get the vuex store context from there. What I ended up using was the nuxtServerInit() action because as stated in the docs:
If nuxt generate is ran, nuxtServerInit will be executed for every dynamic route generated.
This is exactly what I need so I'm trying to use it with the following code:
store/index.js
export const actions = {
nuxtServerInit (context, nuxtContext) {
context.commit("dynamicRoutes/addRoute", nuxtContext)
}
}
store/dynamicRoutes.js
export const state = () => ({
navMenuNivel0: {}
})
export const mutations = {
addRoute (state, { ssrContext }) {
//Ignore static generated routes
if (!ssrContext.payload || !ssrContext.payload.entrada) return
//If we match this condition then it's a nivel0 route
if (!ssrContext.payload.navMenuNivel0) {
console.log(JSON.stringify(state.navMenuNivel0, null, 2));
//Store nivel0 route, we could use url only but only _id is guaranteed to be unique
state.navMenuNivel0[ssrContext.payload._id] = {
url: ssrContext.url,
entrada: ssrContext.payload.entrada,
navMenuNivel1: []
}
console.log(JSON.stringify(state.navMenuNivel0, null, 2));
//Nivel1 route
} else {
//...
}
}
}
export const getters = {
navMenuNivel0: state => state.navMenuNivel0
}
The action is indeed called and I get all the expected values, however it seems like that with each call of nuxtServerInit() the store state gets reset. I printed the values in the console (because I'm not sure even if it's possible to debug this) and this is what they look like:
{}
{
"5fc2f4f15a691a0fe8d6d7e5": {
"url": "/A",
"entrada": "A",
"navMenuNivel1": []
}
}
{}
{
"5fc2f5115a691a0fe8d6d7e6": {
"url": "/B",
"entrada": "B",
"navMenuNivel1": []
}
}
I have searched all that I could on this subject and altough I didn't find an example similar to mine, I put all the pieces I could together and this was what I came up with.
My idea was to make only one request to the API (during build time), store everything in vuex then use that data in the components and pages.
Either there is a way of doing it better or I don't fully grasp the nuxtServerInit() action. I'm stuck and don't know how to solve this problem and can't see another solution.
If you made it this far thanks for your time!
I came up a with solution but I don't find it very elegant.
The idea is to store the the API requests data in a static file. Then create a plugin to have a $staticAPI object that expose the API data and some functions.
I used the build:before hook because it runs before generate:before and builder:extendPlugins which means that by the time the route generation or plugin creation happen, we already have the API data stored.
dynamicRoutesGenerator.js
const generator = function () {
//Add hook before build to create our static API files
this.nuxt.hook('build:before', async (plugins) => {
//Fetch the routes and pages from API
let navMenuRoutes = await APIService.fetchQuery(QueryService.navMenuRoutesQuery())
let pages = await APIService.fetchQuery(QueryService.paginasQuery())
//Cache the queries results into staticAPI file
APIService.saveStaticAPIData("navMenuRoutes", navMenuRoutes)
APIService.saveStaticAPIData("pages", pages)
})
//Before hook to generate our custom routes
this.nuxt.hook('generate:before', async (generator, generatorOptions) => {
console.log('generate:before')
generator.generateRoutes(await generateDynamicRoutes())
})
}
//Here I can't find a way to access via $staticAPI
let generateDynamicRoutes = async function() {
let navMenuRoutes = APIService.getStaticAPIData("navMenuRoutes")
//...
}
The plugin staticAPI.js:
import APIService from '../services/APIService'
let fetchPage = function(fetchUrl) {
return this.pages.find(p => { return p.url === fetchUrl})
}
export default async (context, inject) => {
//Get routes and files from the files
let navMenuRoutes = APIService.getStaticAPIData("navMenuRoutes")
let pages = APIService.getStaticAPIData("pages")
//Put the objects and functions in the $staticAPI property
inject ('staticAPI', { navMenuRoutes, pages, fetchPage })
}
The APIService helper to save/load data to the file:
//...
let fs = require('fs');
let saveStaticAPIData = function (fileName = 'test', fileContent = '{}') {
fs.writeFileSync("./static-api-data/" + fileName + ".json", JSON.stringify(fileContent, null, 2));
}
let getStaticAPIData = function (fileName = '{}') {
let staticData = {};
try {
staticData = require("../static-api-data/" + fileName + ".json");
} catch (ex) {}
return staticData;
}
module.exports = { fetchQuery, apiUrl, saveStaticAPIData, getStaticAPIData }
nuxt.config.js
build: {
//Enable 'fs' module
extend (config, { isDev, isClient }) {
config.node = { fs: 'empty' }
}
},
plugins: [
{ src: '~/plugins/staticAPI.js', mode: 'server' }
],
buildModules: [
'#nuxtjs/style-resources',
'#/modules/staticAPIGenerator',
'#/modules/dynamicRoutesGenerator'
]

Vue updates data without waiting for state file to update it

When the page is being loaded for the first time, vue component is not waiting for my custom store file to process it. I thought it might fix it with promises but I am not sure on how to do so on functions that do not really require extra processing time.
I am not including the entire .vue file because I know it surely works just fine. My store includes couple of functions and it is worth mentioning it is not set up using vuex but works very similarly. Since I also tested what causes the issue, I am only adding the function that is related and used in MainComp.
Vue component
import store from "./store";
export default {
name: "MainComp",
data() {
return {
isLoading: true,
storageSetup: store.storage.setupStorage,
cards: Array,
};
},
created() {
this.storageSetup().then(() => {
this.cards= store.state.cards;
});
this.displayData();
},
methods: {
displayData() {
this.isLoading = false;
},
}
My custom store.js file
const STORAGE = chrome.storage.sync;
const state = {
cards: []
};
const storage = {
async setupStorage() {
await STORAGE.get(['cards'], function (data) {
if (Object.keys(data).length === 0) {
storage.addToStorage('ALL');
// else case is the one does not work as required
} else {
data.cards.forEach((elem) => {
// modifies the element locally and then appends it to state.cards
actions.addCard(elem);
});
}
});
}
};
export default {
state,
storage
};
Lastly, please ignore the case in setupStorage() when the length of data is equal to 0. If there is nothing in Chrome's local space, then a cards is added properly(state.cards is an empty array every time the page loads). The problem of displaying the data only occurs when there are existing elements in the browser's storage.
How can I prevent vue from assuming cards is not an empty array but instead wait until the the data gets fetched and loaded to state.cards (i.e cards in MainComp)?
Sorry if the problem can be easily solved but I just lost hope of doing it myself. If any more information needs to be provided, please let me know.
Your main issue is that chrome.storage.sync.get is an asynchronous method but it does not return a promise which makes waiting on it difficult.
Try something like the following
const storage = {
setupStorage() {
return new Promise(resolve => { // return a promise
STORAGE.get(["cards"], data => {
if (Object.keys(data).length === 0) {
this.addToStorage("All")
} else {
data.cards.forEach(elem => {
actions.addCard(elem)
})
}
resolve() // resolve the promise so consumers know it's done
})
})
}
}
and in your component...
export default {
name: "MainComp",
data: () => ({
isLoading: true,
cards: [], // initialise as an array, not the Array constructor
}),
async created() {
await store.storage.setupStorage() // wait for the "get" to complete
this.cards = store.state.cards
this.isLoading = false
},
// ...
}

Vuex Mutation running, but component not updating until manual commit in vue dev tools

I have a vue component that I can't get to update from a computed property that is populated from a service call.
Feed.vue
<template>
<div class="animated fadeIn">
<h1 v-if="!loading">Stats for {{ feed.name}}</h1>
<h2 v-if="loading">loading {{ feedID }}</h2>
</div>
</template>
<script>
export default {
data: () => {
return {
feedID: false
}
},
computed: {
feed(){
return this.$store.state.feed.currentFeed
},
loading(){
return this.$store.state.feed.status.loading;
}
},
created: function(){
this.feedID = this.$route.params.id;
var fid = this.$route.params.id;
const { dispatch } = this.$store;
dispatch('feed/getFeed', {fid});
}
}
</script>
That dispatches 'feed/getFeed' from the feed module...
feed.module.js
import { feedStatsService } from '../_services';
import { router } from '../_helpers';
export const feed = {
namespaced: true,
actions: {
getFeed({ dispatch, commit }, { fid }) {
commit('FeedRequest', {fid});
feedStatsService.getFeed(fid)
.then(
feed => {
commit('FeedSuccess', feed);
},
error => {
commit('FeedFailure', error);
dispatch('alert/error', error, { root: true });
}
)
}
},
mutations: {
FeedRequest(state, feed) {
state.status = {loading: true};
state.currentFeed = feed;
},
FeedSuccess(state, feed) {
state.currentFeed = feed;
state.status = {loading: false};
},
FeedFailure(state) {
state.status = {};
state.feed = null;
}
}
}
The feedStatsService.getFeed calls the service, which just runs a fetch and returns the results. Then commit('FeedSuccess', feed) gets called, which runs the mutation, which sets state.currentFeed=feed, and sets state.status.loading to false.
I can tell that it's stored, because the object shows up in the Vue dev tools. state.feed.currentFeed is the result from the service. But, my component doesn't change to reflect that. And there is a payload under mutations in the dev tool as well. When manually commit feed/feedSuccess in the dev tools, my component updates.
What am I missing here?
In the same way that component data properties need to be initialised, so too does your store's state. Vue cannot react to changes if it does not know about the initial data.
You appear to be missing something like...
state: {
status: { loading: true },
currentFeed: {}
}
Another option is to use Vue.set. See https://vuex.vuejs.org/guide/mutations.html#mutations-follow-vue-s-reactivity-rules...
Since a Vuex store's state is made reactive by Vue, when we mutate the state, Vue components observing the state will update automatically. This also means Vuex mutations are subject to the same reactivity caveats when working with plain Vue
Hey for all the people coming to this and not being able to find a solution. The following was what worked for me:
Declaring base state:
state: {
mainNavData: [],
}
Then I had my action which is calling the now fixed mutation:
actions : {
async fetchMainNavData({ commit }) {
var response = await axios.get();
commit('setMainNavData', response));
},
};
Now my mutation is calling this updateState() function which is key to it all
mutations = {
setMainNavData(state, navData) {
updateState(state, 'mainNavData', navData);
},
};
This is what the updateState function is doing which solved my issues.
const updateState = (state, key, value) => {
const newState = state;
newState[key] = value;
};
After adding updateState() my data reactively showed up in the frontend and I didn't have to manually commit the data in Vue tools anymore.
please note my store is in a different file, so its a little bit different.
Hope this helps others!
Sometimes updating property that are not directly in the state is the problem
{
directprop: "noProblem",
indirectParent: {
"test": 5 // this one has a problem but works if we clone the whole object indirectParent
}
}
but it is a temporary solution, it should help you to force update the state and discover what is the real problem.

VUE 2.0 - Unable to get props id through $router.push to details page

I have a list of Tickets which works fine (companytickets), and when clicked.. it opens up a details page (companyticket) for that specific ticket, passing the id to the component.
problem is i can't find out how to access this prop parameter in the created event, since it's not accessable through "this".
companytickets.vue :
viewTicket: function(ticket){
this.$router.push('/companyticket/' + ticket.Id)
// works : this redirects to http://localhost:8180/companyticket/3
}
companyticket.vue
export default {
name: 'CompanyTicket',
props: {
id: {
type: Number,
required: true
}
},
created() {
this.$store.dispatch('getCompanyTicket', this.id)
// ERROR : this.id is undefined...
console.log("Created here :")
}
}
route config
{ path: '/companyticket/:id', component: CompanyTicket, props: true }
Scenario
this.id is "undefined"
when using this.$route.params.id i get the correct id parameter, but in some weird way it claims to use "companytickets/2" (which is the parent page). The Correct should be companyticket/2.
Screenshot of Chrome Dev :
Use object-style or payload for passing params to actions.
Change:
this.$store.dispatch('getCompanyTicket', this.id)
To:
this.$store.dispatch("getCompanyTicket", {
id: this.id
})
Now your files looks like this:
companyticket.vue
created() {
this.$store.dispatch("getCompanyTicket", {
id: this.id
})
}
store.js
actions: {
getCompanyTicket({ commit }, { id }) {
console.log("ID is available now-->", id)
}
}
Vuex
Since you're using Vuex state management pattern, that would be another approach to share data between component.
It allow parent-child communication and same for child-parent (sharing data with props allow only parent-child communication). Inject store into to your root component:
const app = new Vue({
el: '#app',
// provide the store using the "store" option.
// this will inject the store instance to all child components.
store,
})
This is everything you need in your store object:
var store = new Vuex.Store({
state: {
ticketID: Number
},
mutations: {
UPDATE_TICKET_ID(state, ticketId) {
state.ticketId = ticketId;
}
},
actions: {
getCompanyTicket({ commit, state }, { id }) {
commit("UPDATE_TICKET_ID", id)
}
}
}
Also if you want to update state:
The only way to actually change state in a Vuex store is by committing
a mutation
Any property from state will be available in every component:
console.log(this.$store.state.ticketId)

load json of data into vuex store and access in component

I am trying to load a JSON file of content into my vuejs app and accessing it in my components. I am able to load the json into the vuex store by creating an API:
import Vue from 'vue';
const Http = new Vue();
export function getData() {
return Http.$http.get('./app/assets/content/en_uk.json')
.then(response => Promise.resolve(response.data))
.catch(error => Promise.reject(error));
}
and an action
export const getSiteContent = ({commit}) => {
api.getData().then(data => {
commit('siteContent', data);
});
};
I run getSiteContent on created function of the main vue instance
export default new Vue({
el: '#root',
store,
router,
created() {
getSiteContent(store);
},
render: h => h('router-view')
});
using the vue debug tool in chrome i can see the store
export const state = {
isSearching: false,
searchQuery: '',
siteData: {},
filteredComponents: [],
hasResults: false,
activeComponent: null
};
gets updated with the siteData.
This is part of the json:
{
"global": {
"project_name": {
"text": "Project title"
},
"search": {
"form_placeholder": {
"text": "Search..."
},
"no_results": {
"text": "Sorry no results for '{0}' was found"
},
"search_text": {
"text": "You are searching for '{0}' and there are {1} found"
}
}
}
}
When I try and access
computed: {
...mapGetters(['siteData']),
mumbo () {
return this.siteData.global.project_name;
}
}
in my component like {{mumbo}} I get cannot read property of project_name of undefined.
I feel like this is a time issue as it doesn't fall over when I set it to return siteData.global
I'm not sure if I am doing something wrong or I am missing a connection to get this to work.
As you guessed the problem here is that Vue is trying to access the contents of siteData for that computed property while the data is still loading. Although siteData is a valid object initially, trying to access siteData.global.project_name fails because siteData has no field global when the data hasn't loaded yet. To prevent the error, you will have to include a check like this:
mumbo () {
return this.siteData.global ? this.siteData.global.project_name : 'Loading...';
}
To illustrate the solution, here's a simple JSFiddle based on your code.

Categories

Resources