VUE3 - VUEX v-model on an object - javascript

I am using Vue3's Composition API and would like to store some search parameters in my store.
My state:
const state = () => ({
selection: {
selected_cabins: "M",
flight_type: "round",
adults: 1,
children: 0,
infants: 0,
pets: 0,
fly_from: "",
fly_to: "",
start_date: "",
return_date: "",
},
});
I'm trying to use it like so:
<q-select
borderless
:options="flightType"
v-model="selection.flight_type"
option-value="value"
emit-value
map-options
/>
Computed Property:
const selection = computed({
get() {
return store.state.flights.selection;
},
set(val) {
store.commit("flights/SET_SELECTION", val);
},
});
But I am still getting the error that I cannot mutate state outside of a mutation.
If I break the object (selections) down to its properties I can get it to work, but thats really verbose. Is there anyway to do the above with an object as I have it?

In v-model you're accessing the nested state value which mutates that state, the right syntax should be like :
v-model="flight_type"
and in the setter spread the state with modified property :
const flight_type = computed({
get() {
return store.state.flights.selection.flight_type;
},
set(val) {
store.commit("flights/SET_SELECTION",
{...store.state.flights.selection,flight_type:val);
},
});

Related

why this.setState going to "is not a function"?

Im trying build simple app to learn how to make api calls.
When I'm trying to setState in react with onClick function I'v created, every time I'v tried to invoke I get
not a function
I tried to bind this like this:
this.setState(() => ({
rowPos: null
}).bind(this))
but that did not worked also, I get the error:
(intermediate value).bind is not a function
there is constructor with state object:
constructor(props) {
super(props);
this.state = {
endpoint: 'https://jolapatola5.fakturownia.pl/invoices.json',
params: {
api_token: 'B5Lg3uPBCMcDNX5lsQOM/jolapatola5',
invoice: {
"kind": "vat",
"number": null,
"sell_date": "2019-07-14",
"place": 'Lublin',
"sell_date": "2019-07-14",
"issue_date": "2019-07-14",
"payment_to": "2019-07-21",
"buyer_name": "aaa",
"buyer_tax_no": "5252445767",
"buyer_street": "aaa",
"buyer_post_code": "",
"buyer_city": "",
"seller_name": 'aaa',
"seller_street": '',
"seller_post_code": '',
"seller_city": '',
"seller_bank_account": '',
"seller_tax_no": '',
positions: [{
"name": "Produkt A1",
"tax": 23,
"total_price_gross": 10.23,
"quantity": 1
},
{
"name": "Produkt A1",
"tax": 23,
"total_price_gross": 10.23,
"quantity": 1
}
]
}
}
}
this.removeProductRow = this.removeProductRow.bind(this);
}
and method I'm trying to invoke onClick:
removeProductRow(id) {
let rowPos = this.state.params.invoice.positions[id];
this.setState(() => ({
rowPos: null
}).bind(this))
console.log(rowPos)
};
id is passed when I'm mapping components
The result I'm trying to perform is set the this.state.params.invoice.position alias rowPos to null, now its an object.
Thanks for any help
EDIT: there is the way I'm mapping components:
{
this.state.params.invoice.positions.map(function(item,index){
return
<ItemRow key={index} removeProductRow={() => this.removeProductRow(index)}/>
},this)
}
setState should be binded to React.Component, when you call this.setState.bind(this) you are actually binding it to removeProductRow, just remove the .bind(this)
There are two things I would do different.
First: Fixing the remove method.
removeProductRow(index){
let positionsUpdated = this.state.params.invoice.positions.filter((_, idx) => idx !== index);
// update just the positions of the state.
// try to create paramsUdpated yourself.
this.setState({params: paramsUdpated});
};
Second: In render I would not pass callback to props, just the name of the function and use prop itemIndex to get the index of the positions array in ItemRow component.
{this.state.params.invoice.positions.map(function(item,index){
return (<ItemRow key={index} itemIndex={index} removeProductRow={this.removeProductRow}/>)}
Working example of my idea: https://codesandbox.io/s/priceless-sun-tb76r

How can I get Vue.js to be reactive for changes to dynamically added array properties?

In Vue.js, I have a data object with dynamically added/edited properties that are themselves arrays. For example, the property starts out as follows:
data: function () {
return {
vals: {}
};
}
And over time, through various button clicks, etc., vals may look like the following (with the actual property names and values being 100% dynamic based on a number of factors):
vals: {
set1: [
{
prop1: 123,
prop2: 'hello'
},
{
prop1: 456,
prop2: 'bye'
}
],
set2: [
{
prop3: 'Why?!',
prop4: false
}
]
}
As the array properties (i.e., set1 and set2) are changed, I want to be able to react to those changes.
For example, I may do something like the following in my code:
var prop = 'set1';
this.vals[prop].push({
{
prop1: 789,
prop2: 'hmmm...'
}
});
However, when I do that, the component is not updating (I presume because I am pushing an object onto the end of a subarray of an object; and Vue.js doesn't seem to track those changes).
I have been able to force the component to be reactive by doing this.$forceUpdate(); after the above push, but I imagine there has to be a more eloquent way of getting Vue.js to be reactive when it comes to objects being pushed onto the end of object subarrays.
Does anyone know of a better way to try to do what I am trying to achieve? Thank you.
Any time you're adding a new property to an object or changing a value within an array, you need to use Vue.set().
Vue.set(this.vals, prop, [ /* ... */ ])
Ideally, you should define all your properties up front so Vue doesn't have to invalidate computed properties depending on your data model's shape. Even if you have them set to null you should try to map out all the properties you expect your component to need.
Also, in your first code block, you have a colon after your return: which would evaluate to a label, meaning your data function isn't returning anything.
You could try something like this using lodash and a deep watcher..
More on deep watcher here
new Vue({
el: "#app",
methods: {
setValue() {
this.oldPeople = _.cloneDeep(this.people);
}
},
mounted() {
this.setValue();
},
el: "#app",
data: {
changed: null,
people: [
{ id: 0, name: "Bob", age: 27 },
{ id: 1, name: "Frank", age: 32 },
{ id: 2, name: "Joe", age: 38 }
],
oldPeople: []
},
watch: {
people: {
deep: true,
handler(after, before) {
// Return the object that changed
let vm = this;
let changed = after.filter(function(p, idx) {
return Object.keys(p).some(function(prop) {
return p[prop] !== vm.oldPeople[idx][prop];
});
});
vm.setValue();
this.changed = changed;
},
}
}
});
input {
display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.6/vue.js"></script>
<script src="https://cdn.jsdelivr.net/lodash/4.17.2/lodash.min.js"></script>
<div id="app">
<div>
<input type="text" v-for="(person, index) in people" v-model="people[index].age" />
<div v-if="changed !== null">
You changed:<br/>{{ changed }}
</div>
</div>
</div>

How to update certain object in state array based on received key from DOM

I am confused about executing spread operator and using it to update state array like that
todos: [
{
id: "1",
description: "Run",
completed: "true"
},
{
id: "2",
description: "Pick John",
completed: "false"
}]
I have objects inside my array, the examples provided after searching are using spread operator to update arrays with single object, how can I update that object with "id" that equals "key" only. My wrong function is
markTaskCompleted(e) {
var key = e._targetInst.key;
this.setState(
{
todoList: // state.todoList is todos variable
[...this.state.todoList, this.state.todoList.map(task => {
if (task.id == key) task.completed = "true";
}) ]
},
this.getTodos
);
}
The result of this array is the same array of todos (spread operator) with array of undefined items.
I have been googling for some time but couldn't really get it.
Instead of destructuring the array and using map, I typically update a single item's value with a single map that replaces the item I am updating and returns the existing value for all other items. Something like this:
this.setState((prevState) => {
return {
todoList: prevState.todoList.map((task) => {
if (task.id === key) {
return { ...task, completed: true };
} else {
return task;
}
}),
};
});
Also, notice that this example passes a function to this.setState rather than an object. If you are updating the state based on the previous state (in this example using todoList from the previous state) you should use the function method. setState is asynchronous and you could get unexpected results from using this.state to compute the new state.

Change property in array with Spread Operator returns object instead of array

I want to change the property of an object similar to this, this is a simplified object with a few properties of the original:
state = {
pivotComuns: [
{
id: 1,
enabled : true
},
{
id: 2,
enabled : true
}
],
otherProperties : "otherProperties"
}
I'm changing the state of enabled like this:
state = {
...state,
pivotColumns: {
...state.pivotColumns,
[2]: {
...state.pivotColumns[2], enabled: !state.pivotColumns[2].enabled
}
}
}
It works, but instead of return an array like I is the pivotComuns property it returns an object, "notice that I change [] for {}":
state = {
pivotComuns: {
{
id: 1
enabled : true
},
{
id: 2,
enabled : true
}
},
otherProperties : "otherProperties"
}
What I'm doing wrong, I need to keep that property an array.
Very late post, but for future reference, you could do the following:
state = {
...state,
pivotColumns: state.pivotColumns.map(pc =>
pc.id === 2 ? {...pc, enabled:!pc.enabled} : pc
)
}
The advantage is that you will not change the object referenced in the "old array", you will instead insert a new object in its place. So if you would like to go back and forth in the state you can now do so.
example:
https://codepen.io/anon/pen/JyXqRe?editors=1111
I don't believe you can use the spread operator in such a way and in fact wouldn't recommend it if you could because it creates very hard to read code. There is a much simpler solution that I use on a daily basis when it comes to updating a key/value on an object where the value is an array:
var state = {
pivotColumns: [
{
id: 1,
enabled : true
}, {
id: 2,
enabled : true
}
],
otherProperties : "otherProperties"
}
var clonedPivotColumns = state.pivotColumns.slice();
clonedPivotColumns[1].enabled = !state.pivotColumns[1].enabled;
state = {
...state,
pivotColumns: clonedPivotColumns
}
this will get you the right results and will not cause any mutations.
working pen
http://codepen.io/finalfreq/pen/ggdJgQ?editors=1111

Deep merge of complex state in React

When I have the following initial state declared:
getInitialState: function() {
return {
isValid: false,
metaData: {
age: 12,
content_type: 'short_url'
}
};
},
and I update state with setState like this:
...
let newMetaData = { age: 20 };
...
this.setState({
isValid: true,
metaData: newMetaData
});
...
Resulting this.state.metadata object has only age defined. But as far as I'm aware, this.setState() merges it argument to existing state. Why it's not working here, isn't this supposed to be recurrent merging?
Is there a way to merge new object properties to state object property in React/ES6?
setState performs a shallow merge. If metaData is is flat:
this.setState({
metaData: Object.assign({}, this.state.metaData, newMetaData),
});
or if using spread :
this.setState({
metaData: { ...this.state.metaData, ...newMetaData },
});
Another way to approach this, if you only need to update one property, would be like this:
this.setState({
metaData: {
...this.state.metaData,
age: 20
}
})
setState can also take a function, which receives an argument of state, and you can use lodash merge to do the deep merge.
setState(state => merge(state, yourPartialObjectToBeDeepMerged));
a tricky solution is here
const [complexObject, setComplexObject] = useState({a:{b:{c:1}}})
setComplexObject((s)=>{
s.a.b.c = 2
return {...s}
})
How does it work?
An object is a reference. If you s.a.b.c = 2, this will update the state, but the component doesn't rerender, so the dom will not change.
Then we need a way to rerender the component. If you setComplexObject(s=>s), it will not trigger rerender, because albeit the interior of the ComplexObject do change, the reference still point at the same object.
So we need ES6 spread operator to reconstruct a object by doing this {...s}

Categories