In my data I have an object program. I load a list of objects asynchronously and add it to program.sections.
async loadSections() {
if (this.user == null && this.program.sections) { return }
await this.$store.dispatch('loadProgramSections', {program: this.program, user: this.user});
console.log('loaded Sections', this.program);
// this.$set(this.program, 'sections', this.progrmm.sections)
// this.$nextTick(() => { this.$forceUpdate(); })
},
My UI looks like this:
<Section :program="program" ></Section>
So I pass my program object down to my Sections component
In my console log I can see that the field programm.sections is indeed my array of objects but my UI does not get changed. When I put my UI code from the Sections component directly in to my page, it gets updated and the data is dispalyed correctly but when I use the component the props are not being updated correctly.
I already tried the commented lines in my code but it doesn't work.
Any Ideas?
Assign a new object reference in this.program:
async loadSections() {
if (this.user == null && this.program.sections) { return }
await this.$store.dispatch('loadProgramSections', {program: this.program, user: this.user});
console.log('loaded Sections', this.program);
// assign a new object reference
this.program = Object.asssign({}, this.program);
},
Also make sure you initialize program in data() section.
The main issue is that Vue cannot detect the changes in some situations.
e.g. set a item to an array or set an additional property to an object
That's the crux of the problem. You might need Caveats.
Related
I am stucked in a problem where I have created a custom drag drop component which is working fine,
there is an array which is connected to vuex store, i have created the mutations to update or add new object inside the array that array is getting changed after drop operation but the ui is not getting changed
I am fetching that array using this.$store.getters['moduleName/functionName']
mutation.js=>
export default {
setNewValue(state, payload){
state.arr.push(payload);
}
}
handleDrop function =>
handleDrop(event, data){
let actionType = this.$store.getters["moduleName/getActionType"];
let obj;
let length = this.$store.getters["moduleName/getLengthOfArr"];
obj = {
id: length == 0 ? 1 : length + 1,
name: data.name,
isConditional: false,
...actionType[data.type],
yes: [],
no: [],
};
this.$store.commit("moduleName/setNewValue", obj);
action Type are some objects which i need to add based on data type provided
i guess it is due to reactivity of object properties or due to computed property which i am using in ui
computed : {
arr(){
return this.$store.getters['moduleName/getArray'];
}
}
getters.js
export default {
getArray(state){
return state.arr;
}
}
Thanks in advance
After some debugging i solved the problem as the problem was with rendering the data was getting updated and even store was getting updated
while rendering i was using the same object which was rendered previously but that made me so much confused due to vuejs' reactivity with computed property.
I've read multiple similar questions about this here and elsewhere, but I can't figure it out.
I have a form with mapGetters and input values that should update based on Vuex state:
...mapGetters({
show: "getShow"
}),
sample form input (I'm using Bootstrap Vue):
<b-form-input
id="runtime"
name="runtime"
type="text"
size="sm"
v-model="show.runtime"
placeholder="Runtime"
></b-form-input>
Then I have this method on the form component:
async searchOnDB() {
var showId = this.show.showId;
if (!showId) {
alert("Please enter a showId");
return;
}
try {
await this.$store.dispatch("searchShowOnDB", showId);
} catch (ex) {
console.log(ex);
alert("error searching on DB");
}
},
and this action on the store:
async searchShowOnDB({ commit, rootState }, showId) {
var response = await SearchAPI.searchShowOnDB(showId);
var show = {
show_start: response.data.data.first_aired,
runtime: response.data.data.runtime,
description: response.data.data.overview
};
//I'm updating the object since it could already contain something
var new_show = Object.assign(rootState.shows.show, show);
commit("setShow", new_show);
}
mutation:
setShow(state, show) {
Vue.set(state, "show", show);
}
searchAPI:
export default {
searchShowOnDB: function (showId) {
return axios.get('/search/?id=' + showId);
},
}
Everything works, the API call is executed, I can even see the Vuex updated state in Vue Devtools, but the form is not updated.
As soon as I write something in an input field or hit commit in Vue Devtools, the form fields show_start, runtime, description all get updated.
Also, this works correctly and updates everything:
async searchShowOnDB({ commit, rootState }, showId) {
var show = {
show_start: "2010-03-12",
runtime: 60,
description: "something"
};
//I'm updating the object since it could already contain something
var new_show = Object.assign(rootState.shows.show, show);
commit("setShow", new_show);
}
I don't know what else to do, I tried by resolving Promises explicitly, remove async/await and use axios.get(...).then(...), moving stuff around... nothing seems to work.
On line 15 of your /modules/search.js you're using Object.assign() on rootState.search.show. This mutates the search prop of the state (which is wrong, btw, you should only mutate inside mutations!). Read below why.
And then you're attempting to trigger the mutation. But, guess what? Vue sees it's the same value, so no component is notified, because there was no change. This is why you should never mutate outside of mutations!
So, instead of assigning the value to the state in your action, just commit the new show (replace lines 15-16 with:
commit('setShow', show);
See it here: https://codesandbox.io/s/sharp-hooks-kplp7?file=/src/modules/search.js
This will completely replace state.show with show. If you only want to merge the response into current state.show (to keep some custom stuff you added to current show), you could spread the contents of state.show and overwrite with contents of show:
commit("setShow", { ...rootState.search.show, ...show });
Also note you don't need Vue.set() in your mutation. You have the state in the first parameter of any mutation and that's the state of the current module. So just assign state.show = show.
And one last note: when your vuex gets bigger, you might want to namespace your modules, to avoid any name clashes.
All props of objects in a state that is used in templates must exist or you should call Vue.set for such properties.
state: {
show: {
runtime: null // <- add this line
}
},
You call Vue.set for the whole object but it already exist in the state and you do not replace it by a new one you just replace props. In your case you have an empty object and add the 'runtime' prop it it using Object.assign.
Also all manipulations with state should be done in mutations:
var new_show = {
runtime: response.data.url
};
commit("setShow", new_show);
...
mutations: {
setShow(state, new_show) {
Object.assign(state.show, new_show)
}
},
I am currently experiencing an issue where the computed() property is not able to get data. Although data was already initiated in created() property. Am I doing it wrong? Please advise how I can fix this issue.
const randomPlayers = {
template:
`
<input type="text" v-model="search_player">
<div v-for="player in modPlayers" v-if="list_of_random_players!=null">
<p>{{player.firstname}}</p>
<p>{{player.lastname}}</p>
<div>
`,
props: ['data'],
data (){
return{
list_of_random_players: null,
search_player: null
}
},
created(){
this.get_random_players()
},
computed: {
modPlayers(){
return this.list_of_random_players.filter( person => {
return !this.search_player ||
( person.firstname.toLowerCase().indexOf(this.search_player.toLowerCase()) > -1 || person.lastname.toLowerCase().indexOf(this.search_player.toLowerCase()) > -1)
})
}
},
methods: {
get_random_players: function(){
$.post(
url:'random_url'
data: {
players: data
}
).done((success)=>{
this.list_of_random_players: JSON.parse(success)
})fail((err)=>{
console.log(err)
})
}
}
}
I get the following two errors:
(1) TypeError: Cannot read property 'filter' of null
(2) TypeError: this.list_of_random_players.filter is not a function
From Vue: "When a Vue instance is created, it adds all the properties found in its data object to Vue’s reactivity system. When the values of those properties change, the view will “react”, updating to match the new values."
So data is a function that returns an object but as mentioned by #Sovalina you are not returning it so you cannot access its properties. You need to add return and change null to []:
data() {
return {
list_of_random_players: [],
search_player: []
}
},
or you can do without return and like a regular object:
data: {
list_of_random_players: [],
search_player: []
}
When your Vue component is used multiple times, it is better to use it like a function(first case).
"When defining a component, data must be declared as a function that returns the initial data object. Why? Because there will be many instances created using the same definition. If we still use a plain object for data, that same object will be shared by reference across all instance created! By providing a data function, every time a new instance is created we can call it to return a fresh copy of the initial data."
Reference:link
It might be just a typo but you need to add : to methods as well.
By running the following code (a Vue.js component), I expect that, after the AJAX call returns, both the v-html directive and the console.log() display the same value.
On the contrary, v-html is stuck with "loading...(1)" even though obj.html has a different value, as console.log() confirms.
The behaviour is caused by getObject overwriting obj, and being afterwards obj.html undefined for a short time before getHTML returns (all this happens in function created).
Can please someone explain whether this is Vue's desired behavior (doc links are welcome), or whether should I submit a bug report, or finally whether I am simply structuring my code in a bad way?
Thanks in advance
<template>
<main v-html="obj.html || 'loading... (1)'">
</main>
</template>
<script>
export default {
name: 'Post',
data: function () {
return {
obj: {
html: 'loading... (2)'
}
}
},
created: async function () {
this.obj = await this.getObject()
this.obj.html = await this.getHtml()
console.log(this.obj.html)
},
methods: {
getObject: async function () {
const resp = await this.$http.get('https://jsonplaceholder.typicode.com/todos')
return resp.body[0]
},
getHtml: async function () {
const resp = await this.$http.get('https://jsonplaceholder.typicode.com/todos')
return resp.body[0].title
},
}
}
</script>
The function getObject returns a String so at the first line of created hook
this.obj = await this.getObject()
you change the reference of the obj and you make it pointing to a string and then you try to put a property on a string, which does not work ;)
it's like you would do
this.obj = 'test'
then console.log(this.obj);
// test
and then this.obj.abc = 'whatever'
console.log(this.obj.abc);
// undefined
You would need to parse the object before, see JSON.parse(string)
Update:
If this is not the case i.e you somehow have an object coming from that service.
Then the only problem I can think is that you lose the reference of your original obj and v-html is still pointing to the old one. In that case you have to avoid modification of the root obj or you can use the vue $set method: https://v2.vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats
It seems vue data objects are not deeply reactive, which means that altering a property will not trigger change detection in the template.
Try rearranging the created hook to compose the full object before assigning it to the data property. That way when the template reacts it will see the html property of obj.
Ref CodeSandbox
created: async function () {
const fetchedObj = await this.getObject()
fetchedObj.html = await this.getHtml()
this.obj = fetchedObj;
console.log(this.obj.html)
},
I don't know how to organize my Vuex store given the following problem.
I have an array of buttons / actions, like 100s of them. They are are organized in the store like this:
buttons: [
{
text: 'Button 1',
doAction (store) {},
mustShow (store) {
return state.variable > 10 && state.variable2.counter < 12 && !state.variable3
}
}
...
]
I can easily display them in my view and link their action to the click event:
<button v-for"button in buttons" #click="button.doAction()"></button>
The problem is that each button can be shown or not based on arbitrary complex logic that it only knows, as you can see in the mustShow function. Each button has its distinctive logic.
I can easily make a getter that returns only the buttons whose mustShow function returns true to have only the actions that must be shown in a specific state of the store:
availableActions (state) {
return state.buttons.filter(s => s.mustShow())
}
This works the first time, but the problem is that of course this getter is not reactive since it's not bound to state variables but to the result of a function that is not reactive.
How would you organize the code to make this work? Of course one could put all the display logic for all the buttons into a single getter. But what if I want the name of the button to be dynamic as well (as the result of a function that computes its value based on arbitrary variables in the state)?
Thanks
I think you are going the wrong way here: as a thumb rule you shouldn't have complex objects, like function definitions, defining your store state. A way of thinking about the store state is that should be something that you should be able to encode in JSON, give it to a friend, and then your friend if parses it back and use it in the same program should get the same result, so clearly a function inside the state won't fit this.
My suggestion would be to do something like:
const state = {
buttons: [
{
text: 'Button 1',
id: 1
},
...
]
}
...
const actions = {
doAction ({commit}, {btnId}) {
// now you perform the action you want to do
...
// finally if you want to change the state of your store you
// should commit a mutation, *do not change the state here!*
// let the mutation do their job
// here you put all the things the mutation may need to perform
// the change of the state
const payload = { btnId }
commit(changeSomethingInState, { payload })
}
}
const mutations = {
changeSomethingInState (state, { payload }) {
state.something = payload
}
...
This is in the store definition. Now in your view you do like:
<button v-for"button in buttons" #click="dispatch('doAction', { btnId: button.id })"/>