Vue using v-for to render computed properties after loaded - javascript

I'm using v-for to iterate over a computed property, and that computed property depends on a data attribute, which is initiated as null. I will load it in beforeMount.
here is the pseudo-code:
<th v-for="item in computed_list">
{{ item.name }}
</th>
<script>
export default {
name: 'test',
data () {
return {
whole_list: null
}
},
beforeMount () {
this.load()
},
computed: {
computed_list: function() {
if (!this.series) return []
return this.whole_list.slice(1,3)
}
},
methods: {
async load () {
let res = await some_api_call()
this.whole_list = res['data']
}
}
}
</script>
But somehow it failed to render the list, and report TypeError: Cannot read property 'name' of null.
I'm new to Vue and not very familiar with its lifecycle. The basic idea is to render list of data, but those data are loaded somehow after the Vue instance is created. Not sure if it's the correct way to do this.

Initializing a data item to null breaks the VueJS state watching functionality so it won't know about changes to it. Initialize it as an empty object or array instead.
https://012.vuejs.org/guide/best-practices.html
The reason for this is that Vue observes data changes by recursively walking the data object and converting existing properties into reactive getters and setters using Object.defineProperty. If a property is not present when the instance is created, Vue will not be able to track it.
You don’t have to set every single nested property in your data though. It is ok to initialize a field as an empty object, and set it to a new object with nested structures later, because Vue will be able to walk the nested properties of this new object and observe them.

Related

Vuex: store.state.activities has a key "list" that is an array of 3 items, however store.state.activities.list returns an empty array

My project is in Vue.
store.state.activities is an object that has 2 keys, one of them is an array called list that has 3 items.
However, when I try to reference it using store.state.activities.list, I get an empty array.
I've tried making both a shallow and a deep copy of store.state.activities, in both cases the copy has an empty list array.
store.state.activities structure:
{
"list": [
{
// some data
},
{
// some data
},
{
// some data
}
],
"dictionaries": {}
}
console.log(store.state.activities) - you can see list has 3 items:
whereas store.state.activities.list returns an empty array:
the usual reasons for this are you've not initialised or accessed the vuex correctly
initialising
is usually done in your main file and should look something like
import { store } from "../store";
createApp(App)
.use(store)
.mount("#app");
accessing
if you are using the Options API you have to access the store via the this in the components, which looks as so
this.$store.state.activities
if you are using the composition API however the this object is not available in which case you should do something like
import {useStore} from "vuex"
...
setup(){
const store = useStore();
return {
activities : computed(()=>store.state.activities),
...
if you are just importing the store object you have defined then you will be getting a uninitialised version that wont be picking up any changes you make
if using type script the code is a little different
the final posibility i can think of is your assignment to the list property
if you are doing
this.$store.state.activities.list = [{},{}.{}];
then you will be replacing the list which will break the reference, assignment should be done via a mutator as vuex will wrap these changes in reactive wrapers that tell watchers of changes
mutations: {
setList(state, value) {
state.activities.list = value;
},
...
},
which would then be called as
store.commit("setList", [{},{},{}]);

How can i react to route change and set value of computed propery even if its value did not change in Nuxt - Vue

I have a list of articles on the page and a load more button, and I need to implement a scroll to article after I come back from the article page to the main page. To keep the articles that were loaded after clicking load more button I store them in state store and the array itself is in the computed property like so:
computed: {
items() {
return this.$store.getters["article-scrollto/getArticlesLoaded"];
},
}
in mounted the init() function is being run and in this function I populate store variable with initial data
this.$store.commit("article-scrollto/setInitialArticles", items);
//and this is the setter
setInitialArticles(state, articles) {
if (state.articlesLoaded.length === 0) {
state.articlesLoaded = [...articles];
console.log({ initsetter: state.articlesLoaded });
}
},
when the load more button is clicked in onCLick function I add data to the state like so:
setArticlesLoaded(state, articles) {
const benchmark = new Set();
state.articlesLoaded.forEach((item) => {
benchmark.add(item.title);
});
articles.forEach((article) => {
if (!benchmark.has(article.title)) {
state.articlesLoaded = [...state.articlesLoaded, article];
}
});
},
And then when I click on the article and the route is changed, and then I come back to the article list technically the store data does not change and my computed property getter does not trigger, and I get no articles even though there are many in the store.
I have tried to repopulate items() computed property in the mounted hook like so:
const storeArticles =
this.$store.getters["article-scrollto/getArticlesLoaded"];
if (storeArticles.length) {
console.log({ storeArticles });
this.items = [...storeArticles];
console.log({ itemsRefilled: this.items });
}
and it does show that there are items in this.items(), but there was no re-render and I get no articles on the page and in the console I have a warning saying that:
[Vue warn]: Computed property "items" was assigned to but it has no setter.
I read about computed setters, and it seems that I can set another data properties value from within computed property, not the value of computed property. Can you please hint me how should I deal with it?
Thanks a lot

Vue Reactivity: Why replacing an object's property (array) does not trigger update

I have a Vue app where I'm trying to make a thin wrapper over the Mapbox API. I have a component which has some simple geojson data, and when that data is updated I want to call a render function on the map to update the map with that new data. A Vue watcher should be able to accomplish this. However, my watcher isn't called when the data changes and I suspect that this is one of the cases that vue reactivity can't catch. I'm aware that I can easily fix this problem using this.$set, but I'm curious as to why this isn't a reactive update, even though according to my understanding of the rules it should be. Here's the relevant data model:
data() {
return{
activeDestinationData: {
type: "FeatureCollection",
features: []
}
}
}
Then I have a watcher:
watch: {
activeDestinationData(newValue) {
console.log("Active destination updated");
if (this.map && this.map.getSource("activeDestinations")) {
this.map.getSource("activeDestinations").setData(newValue);
}
},
}
Finally, down in my app logic, I update the features on the activeDestination by completely reassigning the array to a new array with one item:
// Feature is a previously declared single feature
this.activeDestinationData.features = [feature];
For some reason the watcher is never called. I read about some of the reactivity "gotchas" here but neither of the two cases apply here:
Vue cannot detect the following changes to an array:
When you directly set an item with the index, e.g. vm.items[indexOfItem] = newValue
When you modify the length of the array, e.g. vm.items.length = newLength
What am I missing here that's causing the reactivity to not occur? And is my only option for intended behavior this.set() or is there a more elegant solution?
as default vue will do a shallow compare, and since you are mutating the array rather than replacing, its reference value is the same. you need to pass a new array reference when updating its content, or pass the option deep: true to look into nested values changes as:
watch: {
activeDestinationData: {
handler(newValue) {
console.log("Active destination updated");
if (this.map && this.map.getSource("activeDestinations")) {
this.map.getSource("activeDestinations").setData(newValue);
}
},
deep: true
}
}
If you need to watch a deep structure, you must write some params
watch: {
activeDestinationData: {
deep: true,
handler() { /* code... */ }
}
You can read more there -> https://v2.vuejs.org/v2/api/#watch
I hope I helped you :)

How can I make an Ember computed property depend on all descendent properties of a variable?

I'm trying to create a computed property that I want to be reevaluated whenever any value in a deeply nested object changes. I understand that myObj.[] can be used to reevaluate computed properties whenever any object in an array changes, but I want this to be recursive.
eg I have
// should recalculate whenever myObj.x.y.z changes, or when myObj.a.b.c changes
computed('myObj', function() {
// ...
})
I don't know in advance exactly how the object is structured, and it may be arbitrarily deep.
Neither computed('myObj.[]', ...) nor computed('myObj.#each', ...) seem to work for this.
Any ideas how to do this?
In Ember it is possible to define computed properties at runtime
import { defineProperty, computed } from '#ember/object';
// define a computed property
defineProperty(myObj, 'nameOfComputed', computed('firstName', 'lastName', function() {
return this.firstName+' '+this.lastName;
}));
So taking that a step further, you could dynamically create whatever computed property key string you want at runtime (this could be in component's init() or something):
// define a computed property
let object = {
foo: 'foo',
bar: 'bar'
}
this.set('myObj', object);
let keys = Object.keys(object).map((key) => {
return `myObj.${key}`
});
defineProperty(this, 'someComputed', computed.apply(this, [...keys, function() {
// do something here
}]));
It's up to you to figure out how to properly recursively traverse your objects for all the dependent keys without creating cycles or accessing prototype keys you don't want...or to consider whether or not this is even that good of an idea. Alternatively, you could try to handle the setting of these properties in such a way that retriggers a computation (which would be more in line with DDAU). I can only speculate from what you've provided what works but it's certainly possible to do what you want. See this twiddle in action
could you try anyone computed/obeserver like below..
But try to prefer the computed.
import { observer } from '#ember/object';
import EmberObject, { computed } from '#ember/object';
partOfNameChanged1: observer('myObj','myObj.[]','myObj.#each', function() {
return 'myObj is changed by obeserver';
})
partOfNameChanged2: computed ('myObj','myObj.[]','myObj.#each', function() {
return 'myObj is changed by computed';
})
then in your handlebar/template file
{{log 'partOfNameChanged1 is occured' partOfNameChanged1}}
{{log 'partOfNameChanged2 is occured' partOfNameChanged2}}
Then you have to associate/assign this partOfNameChanged1 / partOfNameChanged2 to some where in the handlebar or to any other variable in your .js file.
As long as you have not assigned this computed/observer property partOfNameChanged1 /partOfNameChanged2 to somewhere, then you will not get it's value.

Vue2 passing arbitrary named variable as prop

I am new to Vue and after checking the docs I can not figure out how to achieve the following:
pass an arbitrarily named variable as a prop to a component instance.
From my understanding, props are meant to be a way to allow data to be passed to a component and as it states on the website:
Passing Data to Child Components with Props:
Props are custom attributes you can register on a component. When a value is passed to a prop attribute, it becomes a property on that component instance.
Since props can be required, it would seem that we can design components under the assumption that some data would be there, and possible within certain parameters (if the validator option is specified).
So I would like to define a function or object outside of vue, e.g. in an application, and pass this function or object to my vue instance.
This works if my named object of function has the exact same name as the prop to which I attempt to bind it. However, as I might have multiple instances of the Vue component and I might want to bind different data, I find using the same name for the variable less than ideal.
Now if I do as the Vue warning suggests, and name object / function the same as the prop, then the warning switches to that my data is not defined inside vue and to make sure it is reactive by reading: https://v2.vuejs.org/v2/guide/components-props.html
which, to be honest, doesnt really explain how to solve the issue,
or move the prop to the data level.
Which I can do (still gives the same warning), but kind of defeats the purpose of having props with my understanding of Vue.
This become more frustrating with anonymous vue instances.
e.g.
<script>
export default {
props: {
// records: {
// default: function(){return{}},
// type: Object
// }
},
data: function() {
return {
records: {} // define even an empty value in data for it to be 'reactive'
}
},
computed: {
fields: function() {
},
keys: function() {
return Object.keys(this.records)
}
},
methods: {
}
}
</script>
trying to use this as a component and set records to var myRecords = {"a": {}} fails:
<my-comp :records="myRecords"/>
So how exactly should I circumvent this? Where should I define my data then? and how should I handle the naming in the case of multiple instances?
A more fledged on example is found on a similar question:
Vue2: passing function as prop triggers warning that prop is already set
So I would like to define a function or object outside of vue, e.g. in an application, and pass this function or object to my vue instance.
It's hard to give a definitive answer because I don't know the specifics of how you have organized your code. Are you using Webpack? Single file components (.vue)? If yes to any of these, then you needn't use global variables in the way you have described in your question.
Your entire Vue app should consist of a single root Vue instance (which you instantiate with new Vue(...), and from there each component is rendered within the root component's template, and templates of those components, and so on.
Looking at the following template:
<my-comp :records="myRecords"/>
myRecords must be a property on the Vue component instance whose template contains the above. It could be declared within the data block, or as a computed property, or a prop, it doesn't matter.
Here's a small example:
<div id="app">
<my-comp :records="myRecords"></my-comp>
</div>
// Obtain records in some way and store it in a global variable
var records = ...
// This is the root Vue instance
new Vue({
el: '#app',
data: {
// You must store the records array in the Vue component like this
// for it to be referenced within the template.
// You can optionally transform the data first if you want.
myRecords: records.filter(r => r.name.startsWith('Bob'))
// ^ ^
// | |
// | +--- the global variable
// |
// +---- the name of the property on the component instance
}
})
Note that MyComp component does not access the records global variable in any way, it only takes its input through the records prop.

Categories

Resources