I am new to Vue and working my way through it.
I have this method
itemClick() {
let item = this.name;
console.log( item );
store.commit( 'updateSelectedItems', item );
},
and this mutation
updateSelectedItems( items ) {
console.log( items );
store.state.selectedItems.splice( 0 );
store.state.selectedItems.push( items );
}
The method console.log outputs the name correctly (it's coming from props). However, from the updateSelectedItems mutation log it outputs this an object containing all of my states.
Thanks for the help.
That's because mutations are given the state as their first argument. The payload should then be put as a second parameter in the mutation's declaration (like so: updateSelectedItems(state, item)).
(See the docs for more details: https://vuex.vuejs.org/en/mutations.html)
Related
I have the following component to quickly configure stops on a delivery/pickup route and how many items are picked up and dropped
and this is the data model, note the 2 is the one next to 'a' on the previous image.
If a click the + or - button, in the first item, it behaves as expected,
But second item doesn't work as expected
I've already checke a couple of posts on object property update likes this ones
Is it possible to mutate properties from an arbitrarily nested child component in vue.js without having a chain of events in the entire hierarchy?
https://forum.vuejs.org/t/nested-props-mutations-hell-internet-need-clarification/99346
https://forum.vuejs.org/t/is-mutating-object-props-bad-practice/17448
among others, and came up with this code:
ADD_ITEM_TO_SELECTED_STOP(state, payload) {
let count = state.selectedStop.categories[payload.catIndex].items[payload.itemIndex].count;
const selectedCat = state.selectedStop.categories[payload.catIndex];
const currentItem = selectedCat.items[payload.itemIndex];
currentItem.count = count + 1;
selectedCat.items[payload.itemIndex] = currentItem;
Vue.set(state.selectedStop.categories, payload.catIndex, selectedCat);
},
and as the button event:
addToItem(item) {
this.$store.dispatch("addItemToSelectedStop", {
catIndex: item.catIndex,
itemIndex: item.itemIndex
})
},
And finally my computed property code:
items() {
let finalArray = [];
this.selectedStop.categories.forEach(
(cat, catIndex) => {
let selected = cat.items.filter((item) => item.count > 0 );
if (selected.length > 0) {
//here we add the catIndex and itemIndex to have it calling the rigth shit
selected = selected.map(val => {
let itemIndex = cat.items.findIndex( itemToFind => itemToFind.id === val.id);
return {
...val,
catIndex: catIndex,
itemIndex: itemIndex,
}})
finalArray = finalArray.concat(selected);
}
});
return finalArray;
}
What confuses me the most is that I have almost the same code in another component, and there it's working as expected, and although the model is changed, the computed property is only recalculated on the first item,
After reading this gist and taking a look again at the posts describing this kind of issue, I decided to give it a try and just make a copy of the whole stored object not just the property, update it, then set it back on vuex using Vue.set, and that did the trick, everything is now working as expected, this is my final store method.
ADD_ITEM_TO_SELECTED_STOP(state, payload) {
let selectedLocalStop = JSON.parse(JSON.stringify(state.selectedStop));
let count = selectedLocalStop.categories[payload.catIndex].items[payload.itemIndex].count;
selectedLocalStop.categories[payload.catIndex].items[payload.itemIndex].count = count + 1;
Vue.set(state,"selectedStop", selectedLocalStop );
//Now we search for this step on the main list
const stepIndex = state.stops.findIndex(val => val.id === selectedLocalStop.id);
Vue.set(state.stops,stepIndex, selectedLocalStop );
},
I had to add the last bit after updating the whole object, because, originally, the array items were updated when the selected item was changed, I guess some sort of reference, but with the object creation, that relationship no longer works "automatic" so I need to update the array by hand
I have to pull in data from 12 specific departments. When my Vue store gets mounted, I fire off some actions to load in these orders via 12 different ajax calls. (These calls are to different API end points and servers).
Once I get a response I commit them to the state. So in my state I would have the following data set to arrays:
state: {
sportsOrders: [],
photographicOrders: []
//And more..
},
Now I need to filter these specific orders based on if they are late, in production, etc. So I have a getter that is setup like so:
getters: {
getDepartmentLateOrders: (state) => (department) => {
let resultArray = []
state.department.forEach(function(order, index) {
let now = new Date()
let orderShipDate = new Date(order.ExpectedShipDate)
let diff = date.getDateDiff(orderShipDate, now)
if(diff < 0) {
resultArray.push(order)
}
})
return resultArray
},
}
And then when I need to use it in a component...
return this.$store.getters.getDepartmentLateOrders(this.department+'Orders')
//for example it will end up passing 'sportsOrders' to the getter
However, when I do this, Vue doesn't use the state data for sportsOrders so I know I'm not accessing it correctly. It says:
vue.runtime.esm.js?ff9b:574 [Vue warn]: Error in render: "TypeError: Cannot read property 'forEach' of undefined"
If I hardcode my getter like so it works as expected though (but I don't want to do this for 12 departments)..
state.sportsOrders.forEach(function(order, index) {
...
}
How can I set this up so I can use this one getter and have it work with all 12 departments? I only want to pass the name of the department if possible.
I am a newbie to react-native, redux and saga and have run into a use case that I have not been able to find a solution for. I understand how to map state to properties and pass around the state between action, reducer and saga. This makes sense to me so far. This is where things seem to get dicey. I have a form that requires a variable number of form fields at any given time depending upon what is returned from the database.
As an example, let's say I have a structure like this:
{
name: ‘’,
vehicleMake: ‘’,
vehicleModel: ‘’,
carLotCity: ‘’,
carLotState: ‘’,
carLotZipCode: ‘’,
localPartsManufacturers: [{name: ‘’, address: ‘’, zipCode}]
}
Everything from name to carLotZipCode would only require one text field, however, the localPartsManufacturers array could represent any number of object that each would need their own set of text fields per each object. How would I account for this with redux as far as mapping the fields to the state and mapping the state to the properties? I am confused about how to begin with this scenario. I understand how to project mapping when the fields are fixed.
I would keep the data as it is coming from the backend. That way you'll avoid normalizing it. I think we just have to be smarter when rendering the fields. Here's what I'm suggesting:
function onTextFieldChange(name, index) {
// either name = `name`, `vehicleMake`, ...
// or
// name = `localPartsManufacturers` and `index` = 0
}
function createTextField(name, index) {
return <input
type='text'
name={ name }
onChange={ () => onTextFieldChange(name, index) } />;
}
function Form({ fields }) {
return (
<div>
{
Object.keys(fields).reduce((allFields, fieldName) => {
const field = fields[fieldName];
if (Array.isArray(field)) {
allFields = allFields.concat(field.map(createTextField));
} else {
allFields.push(createTextField(fieldName));
}
return allFields;
}, [])
}
</div>
);
}
Form receives all the data as you have it in the store. Then we check if the field is an array. If it is an array we loop over the fields inside and generate inputs same as the other properties createTextField. The tricky part here is how to update the data in the store. Notice that we are passing an index when the text field data is changed. In the reducer we have to write something like:
case FIELD_UPDATED:
const { name, index, value } = event.payload;
if (typeof index !== 'undefined') {
state[name][index] = value;
} else {
state[name] = value;
}
return state;
There is nothing preventing you from keeping a list, map, set or any other object in Redux.
The only thing remaining then, is how you map the state to your props, and how you use them. Instead of mapping a single element from the collection to a prop, you map the entire collection to a single prop, and then iterate over the collection in your render method.
In the action you can pass a new collection back, which is comprised of the form fields making up the parts list. Then, your reducer will replace the collection itself.
Or, upon changing an element in the part collection, you can send an action with its id, find it in the collection in the reducer and replace the element that was changed / add the new one / remove the deleted one.
I want to push values in array and pass these values as options of select tag. I did following,
used plugin
import DropDown, {
Select,
Option,
OptionList,
} from 'react-native-selectme';
assigned state as
this.state = {company:[]};
pushing in this array as
for(let i in data.companyRecord)
company.push(data.companyRecord[i].companyname);
and assigning to select tag as
<Select
width={250}
ref="SELECT1"
optionListRef={this._getOptionList.bind(this)}
defaultValue="Select a Company ..."
onSelect={this._company.bind(this)} asyncOptions={this.state.company}>
</Select>
But it is not working. It is showing that
undefined is not an object('evaluating children.length').
Please help me solving this issue.
You can't edit the state like that.
const tempNames = [];
for(let i in data.companyRecord)
tempNames.push(data.companyRecord[i].companyname);
this.setState({ company: tempNames });
Now your state will have the correct values.
But there might still be some problem, because your error might suggest that this.state.company is undefined, however you correctly assigned this.state.company to an empty array before.
This could be due to this.state is undefined. Are you defining your selector in a own created function? And not in your class own render method? In that case you need to bind this to your method.
renderSelector() {
return (<Select
width={250}
ref="SELECT1"
optionListRef={this._getOptionList.bind(this)}
defaultValue="Select a Company ..."
onSelect={this._company.bind(this)} asyncOptions={this.state.company}>
</Select>);
}
In your constructor you need to bind "this" to that method. Like this:
this.renderSelector = this.renderSelector.bind(this);
i want to check the returned value of $http.get() but i get undefined value. Here is my vue js code:
var vm = new Vue({
el: '#permissionMgt',
data: {
permissionID: []
},
methods:{
fetchPermissionDetail: function (id) {
this.$http.get('../api/getComplaintPermission/' + id, function (data) {
this.permissionID = data.permissionID; //permissionID is a data field of the database
alert(this.permissionID); //*****this alert is giving me undefined value
});
},
}
});
Can you tell me thats the problem here?.. btw $http.get() is properly fetching all the data.
You need to check what type is the data returned from the server. If #Raj's solution didn't resolve your issue then probably permissionID is not present in the data that is returned.
Do a colsole.log(data) and check whether data is an object or an array of objects.
Cheers!
Its a common js error. Make the following changes and it will work.
fetchPermissionDetail: function (id) {
var self = this; //add this line
this.$http.get('../api/getComplaintPermission/' + id, function (data) {
self.permissionID = data.permissionID; //replace "this" with "self"
});
},
}
the reason is this points to window inside the anonymous function function()
This is a well known js problem. If you are using es2015 you can use arrow syntax (() => { /*code*/ }) syntax which sets this correctly