Storing props locally (vue2) - javascript

Following:
Passing data from Props to data in vue.js
I have:
https://codesandbox.io/s/u3mr8
which gives the following warning:
(the idea is to avoid mutating props). What side effects can happen in a straightforward copy object operation? I don't get it. The function just saves props into data.
Drag and drop fails with:
Do you really need a setter for a computed prop?
Looking at:
Computed property was assigned to but it has no setter - a toggle component
I've come up with:
https://codesandbox.io/s/39sgo
which is great, no warnings, no errors; it's just that the component no longer renders (fails to get data saved from prop).
Any ideas/suggestions/help/advice would be really, really awesome.

I think the error is thrown because it is not allowed to set within the getter the value from which the computed property is generated. It is a logical loop to modify the initial value while getting the computed results. Instead you can just return the prop value on initial call to getter (if the local value is not yet set).
get() {
if (!this.itemSectionPropsLocal["itemSectionCategory"]) {
return Object.assign({}, this.itemSectionProps)[
"itemSectionCategory"
];
}
return this.itemSectionPropsLocal["itemSectionCategory"];
},
set(value) {
this.itemSectionPropsLocal = value;
},
Also, in setter, you should assign the received value not the prop. If you want to update the local values if the prop value changes after mount you should use a watcher.
watch: {
itemSectionProps: {
deep: true,
handler(val){
this.getPropsLocal = Object.assign({}, val["itemSectionCategory"])
}
}
}

Related

Why using toRef(props) and is it good idea to change their value in CompositionAPI?

I've seen a pattern of using props in of CompositionAPI very often,
that is use toRefs to make all entries of props ref.
I'm kind of confused by it.
For exmaple, from the Vue 3 official guide:
export default {
props: {
user: {
type: String,
required: true
}
},
setup(props) {
const { user } = toRefs(props)
//... use user's value
}
}
I have 2 questions in 2 scenearios:
when the props.user is already reactive
Its value will change if it's changed in any ancestors, so why we need to use toRefs? Doesn't it already reactive?
if it's not reactive, it's just a primitive string value
Does making it reactive means we're going to change its value?
I think making a object reactive also imply that its value is welcomed to be changed.
But all the guides and linters warn us that we'd better not to change the props value.(for not writing to the computed value or something)
If I can change the props value directly in the component, I no longer need to emit the changes to parent component everytime.
It's very convenient but I don't know whenther it is a good idea to change the props value after we're sure it becomes reactive?
Since props aren't supposed to be mutated, this is useful for a specific case that is explained in the documentation; a ref that is computed from a prop needs to be passed as an argument:
const { user } = toRefs(props)
// or
// const user = computed(() => props.user)
someFunction(user);
Where a function makes use of composition API or just needs an argument to be passed by reference rather than by value due to the way it works, e.g.:
function someFunction(val) {
setTimeout(() => {
console.log('Up-to-date value:', unref(val));
}, 1000);
}

How to access mapState property inside the method

How can I access the count property inside the method when I use vuex? Please see my code below.
Code screenshot:
Error
[Vue warn]: Computed property "count" was assigned to but it has no setter.
You can access computed properties just like you access your data properties in a component. But since you are mapping the state in this case. You should not increment or alter its value directly inside the component. Instead, you should dispatch an action with the updated/incremented value and use mutation to mutate the value of the count property in the state object.
More detail https://vuex.vuejs.org/guide/mutations.html#commit-with-payload
The mapState you wrote is inside a computed block. By default computed values are read-only, but you can make them read/write by giving them a setter (ie a function to call when modifying the computed value):
computed: {
count: {
get() { return this.$store.state.count; },
set(newValue) {
// This will most likely throw a warning, as it is bad practise.
this.$store.state.count = newValue;
}
}
}
https://v2.vuejs.org/v2/guide/computed.html#Computed-Setter

How to return a copy of a value from an object in a vuex store?

Importantly, are not passing a whole object back in the getter. Need are passing copie of the object "singleMetricNamesMap" value it's a name for metric were id is property. This is very important because if you pass back an object from the store, then anyone can mutate the store directly by changing the object. This whole class of bugs can be avoided by preferring getters which return primitive types
Here I watch of the metrics that change, and if the condition works, I pass the metric objects to store mutations
watch: {
metric() {
if (this.metric) {
this.setSingleMetricNamesMap({
id: this.metric.id,
name: this.metric.name })
}
}
},
state:
singleMetricNamesMap: {}
state getter were i try to return the metric name by id which I pass from the component
singleMetricNamesMap: state => id => state.singleMetricNamesMap[id]
state mutations were i write metric objects into the state
setSingleMetricNamesMap: (state, val) => {
state.singleMetricNamesMap[val.id] = val.name
},
The component where I pass the metric id to state getter and try to return a copy of the object (store.singleMetricNamesMap) value by id. But after reloading the page, I get undefined on the property of the object, that is, on id, and the names for the metrics appear only after the metric component is changed (for example, edited). Please tell me if I correctly return a copy of the value from the store.singleMetricNamesMap object and how to display metric names immediately when the component loads
<z-dash-tile-config-option
label="Metric"
:value="singleMetricNamesMap(value.metricId)"
>

Vue Router and VueJS reactivity

I've got this code for my vue-router:
{
path: '/templates/:id/items/:itemId', component: Item,
name: 'item'
},
On the item object, I've got a computed property templateId:
templateId() {
return parseInt(this.$route.params.id, 10);
},
The issue I have, is, each time I add an anchor to the url (clicking a link, INSIDE the item component), even if the component doesn't change, this property is computed again.
It means that all computed properties depending of templateId will be computed again.
But the templateId value doesn't change at all.
Here is a really simple jsfiddle to explain the issue:
https://jsfiddle.net/1Lgfn9qh/1/
If I remove the watch property (never called), nothing is logged in the console anymore.
Can you explain me what's happening here?
Why the computed properties are recomputed, even if no values has been updated?
How can I avoid that?
What's causing this behaviour is the fact, that the route object is immutable in Vue. Any successful navigation will result in a completely new route object therefore triggering a re-computation of all computed and watched properties. See https://router.vuejs.org/api/#the-route-object for more details.
To solve this you can watch the route object and filter the relevant vs irrelevant changes for you there
watch: {
'$route' (to, from) {
if(to.path != from.path) { // <- you may need to change this according to your needs!
this.relevantRoute = to
}
}
}
And then reference the manually set variable in your computed and/or watched properties
templateId() {
return parseInt(this.relevantRoute.params.id, 10);
},

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.

Categories

Resources