I'm trying to get a better understanding of the lifecycle of a Nuxt app that is statically generated and could use some help.
I've been running into issues where data attributes will not be set if I rely on mounted to initialize it and I'm not totally sure why.
For example, lets say I have the following setup to load data using fetch and computed properties.
async fetch ({store, params}) {
const data = await store.dispatch('fetchData', params.id)
store.dispatch('setData', data)
},
computed: {
data () {
return this.$store.getters.loadedData
}
}
This works fine, but now if I want to use a v-model I need to add a data property since computed property doesn't make sense here.
data () {
selectedId: null
},
mounted () {
this.selectedId = this.$store.getters.loadedData.id
}
When I do this, this.selectedId is not set if I do a page refresh, it will work the first time if I navigate to the page from another URL. There's something about the order of operations or timing when a page refresh occurs that doesn't run mounted as expected. I know it's running because I can do console.log and see them but the value for this.selectedId is not being set. I'm assuming it's because the store is not ready yet by this point.
So as a hack, I can wrap it with nextTick
mounted () {
this.$nextTick(() => {
this.selectedId = this.$store.getters.loadedData.id
})
}
And it works now... but this doesn't seem right.
What's the best practice here for setting component data that relies on the store? I can't use the fetch method because the component is not accessible here.
Could you use Vuex mapState (https://vuex.vuejs.org/en/state.html#the-mapstate-helper)?
<script>
import {mapState} from 'vuex'
export default {
computed: {
...mapState({
selectedId: state => state.loadedData.id
})
}
}
</script>
But v-model with mapState probably isn't good practice — components should not directly mutate state.
https://vuex.vuejs.org/en/forms.html
Seems an explicit mutation handler is preferable.
@joshuabryant Thanks for looking through it.
My issue isn't how to get Vuex data accessible into the component, it's that I want to initialize the data with some data which comes from the Vuex store... because as you said, v-model with computed properties isn't a good thing to do.
But the problem is that if I set the data in mounted or created, it doesn't always set the data... unless I wrap it with a nextTick delay. It's something going on with the order of operations and when async fetch is ran I believe.
In particular, this happens if you refresh the page, probably because when you refresh the page it's forcing the entire app to run through the lifecycle and mounted must be ran before the async fetch or something.
I'm having the same similar issue and I'm unsure how to correct it. When I hard reload the page, repos are not loaded. But when I navigate to another page, and then click back to index, the repos are loaded. What is the best way to solve this?
// store/index.js
const createStore = () => {
return new Vuex.Store({
state: {
counter: 0,
repos: []
},
actions: {
LOAD_REPOS: function({ commit }) {
axios.get('repourl').then((res) => {
commit('SET_REPOS', res.data)
})
}
},
mutations: {
SET_REPOS: (state, repos) => {
state.repos = repos
},
increment(state) {
state.counter++
}
}
})
}
// pages/index.vue
<script>
import { mapState } from 'vuex'
export default {
fetch ({ store }) {
store.dispatch('LOAD_REPOS')
},
computed: mapState([
'counter',
'repos'
]),
methods: {
increment () {
this.$store.commit('increment')
}
}
}
</script>
Interesting.... your code snippet from my own experience should work. The only difference I have going in my code is that I place async in the fetch and actions so that it's like
async fetch ({ store }) {
}
Where things stop working is when I try to set data through mounted.
Then in this instance I get the same problem where a hard refresh does not work, but navigating to the page does.
Have you tried using async or promises to wrap your API calls? Maybe that will help.
@andreliem Thanks for the tip, do you have an example of wrapping the api call in an async or promise in the store? Something like this?
actions: {
LOAD_REPOS: function({ commit }) {
return new Promise((resolve) => {
axios.get('url').then((res) => {
commit('SET_REPOS', res.data)
resolve()
})
})
}
},
I prefer the async await syntax if you can... cleaner I think.
e.g.
actions: {
async loadStuff ({commit}) {
const data = await this.$axios.$get(...)
commit('UPDATE_DATA', data)
}
}
and in the component it's similar
async fetch ({store, params}) {
const data = await store.dispatch('loadStuff')
// do stuff with data if needed
},
With your style of store though, off the top of my head I don't know the exact syntax. I'm assuming async goes before the LOAD_REPOS.
Well, it appears async was the way to go, it worked! Thank you! Here's the code
async fetch ({ store }) {
if (store.state.repos.length === 0) {
const data = await store.dispatch('LOAD_REPOS')
}
}
actions: {
LOAD_REPOS: async function({ commit }) {
const res = await axios.get('repourl')
commit('SET_REPOS', res.data)
}
}
This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.