I am trying to build a simple filter which sorts an array in Vue. I have data in state, which is updated by api call which is only triggered while loading the page.
Data structure is list of warehouses, each has id and other properties, period object, inside period object there is array of salesagents objects, each of salesagents has id.
Then in component there is an array of warehouses ids and array of salesagents ids. I change the contents of these arrays by clicking on checkboxes for warehouses or salesagents to be visible or not.
I have computedWarehouses computed property which runs when aforementioned visible salesagents or warehouses array is changed.
Warehouses part works fine, when I uncheck checkbox for the warehouse, warehouse is removed from the computedWarehouses array but the warehouse is still in state.data.
When I click again on the checkbox warehouse is again inserted in the computedWarehouses array and everything is good.
Now when I try to do the same for salesagents which are inside of the warehouses period object then I can remove the salesagents from the computedwarehouses array but for some reason they get also removed from state data so unchecking a salesagents checkbox removes the salesagent from computedwarehouses array but checking the checkbox again has no effect because that salesagent is not in the data anymore.
data I get from state via a getter.
computedWarehouses() property code:
computedWarehouses() {
let computedWarehouses = [];
let intWarehousesFilter = this.warehousesFilter.map( (id) => {
return parseInt(id);
});
for (let warehouse in this.data) {
if (intWarehousesFilter.includes(this.data[warehouse].id)) {
computedWarehouses.push(this.data[warehouse]);
}
}
let intConsultantsFilter = this.consultantsFilter.map( (id) => {
return parseInt(id);
});
for (let warehouse in computedWarehouses) {
for (let workday in computedWarehouses[warehouse].period.workdaysObjects) {
let computedSalesAgents = [];
for (let salesAgent in computedWarehouses[warehouse].period.workdaysObjects[workday].salesAgents) {
let salesAgentObject = computedWarehouses[warehouse].period.workdaysObjects[workday].salesAgents[salesAgent];
if (intConsultantsFilter.includes(parseInt(salesAgentObject.id))) {
computedSalesAgents.push(salesAgentObject);
}
}
computedWarehouses[warehouse].period.workdaysObjects[workday].salesAgents = computedSalesAgents;
}
}
return computedWarehouses;
},
What am I doing wrong? I do not want to change the state, I want that the state.data would remain the same all the time, so when I check again some salesagents checkbox I would see the agent again.
I will answer myself.
The solution was to make a copy and break all ties to the original data.
const tempWarehouses = JSON.parse(JSON.stringify(this.data));
I added this line and did all my operations on tempWarehouses so the this.data was left untouched.
Related
I am functional component I am using hooks to update the state of the array locationData
const c = {
title: inputValue,
}
setLocationData([...locationData, c]);
This is working fine, but now I want to add the value at the beginning of the array, and not at the end.
Edit:
I also have a problem to delete an item from the array. I want to delete one item, but more are deleted
const deleteItem = (index) => {
console.log(index)
var temp = locationData;
var temp = locationData.splice(index, 1);
setLocationData(temp);
}
You are almost there, just switch the position in the array.
setLocationData([c, ...locationData]);
You should also be aware of other methods like splice, slice, push, pop etc...
Update: Using Splice
This relates to part 2 of your question with regards to removing from specific index.
The splice() method changes the contents of an array by removing or
replacing existing elements and/or adding new elements in place. To
access part of an array without modifying it, see slice().
So your codes should be similar to the following
var temp = [...locationData];
temp.splice(index, 1);
setLocationData(temp);
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'm using Vuex with a getter that filters a lot of data and then some components present it to the user grouped by status. The user can increment the visible count of elements per status by 5. How many items are visible currently is on the Vuex store and a getter uses this to create a "View object".
When I update this visibility object the getter is no rerun so something in the dependency tracking went south. I'm not adding or deleteing properties, still I'm using Vue.set(...) just to be sure.
This is the mutation that increments the visible amount of items for a status:
viewMore(state, status) {
console.log('viewMore')
const current = state.visibility.statuses[status]
Vue.set(state.visibility.statuses, status, current + 5)
}
This mutation is running well and I can see in the developer tools how the visibility increments reactively with every commit. Now here is the getter that depends on this data:
visibleProspects(state, getters) {
console.log('visibleProspects')
let result = {}
for (const status in getters.sourceData) {
if (!result[status]) {
result[status] = { prospects: [] }
}
getters.sourceData[status].forEach(function(prospect) {
if (result[status].prospects.length < state.visibility.statuses[status])
result[status].prospects.push(prospect)
})
}
return result
}
What this does is traverses a complex getter named sourceData (not shown here for brevity) and then depending on how many visible items there are it returns a new structure with that maximum in an array. visibleProspects is then used by my components and everything runs fine the first time or if a update the data that sourceData computes (e.g adding / editing / deleting a prospect).. but no matter what I do modifying state.visibility.statuses is not forcing visibleProspects to recompute.
How can I debug this?
You can make deep copy to make it reactive (using JSON.parse(JSON.stringify())
viewMore(state, status) {
console.log('viewMore')
const current = state.visibility.statuses[status]
state.visibility.statuses[status] = current + 5
state.visibility = JSON.parse(JSON.stringify(state.visibility))
}
#ittus 's answer should work. But the clone operation would be heavy if your state is big.
alternatively, you may try using Vue.set on the root state state.visibility instead. This should make the reactivity works as expected.
Vue.set(state.visibility, 'statuses', {
...state.visibility.statuses,
[status]: current + 5
})
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 am using extjs 4.1.1a for developing some application.
I had a form consisting of two combo-boxes and an item-selector.
Based on the value selected in first combo-box , the itemselector will load its data from database. This is working fine.
My problem is, if i reselect the first combo-box the new data will be displayed in itemselector along with previous data displayed in itemseletor .That is previous data displayed in itemselector will remain there itself.
for example: name "test1" consists of ids 801,2088,5000. on selecting test1 in firstcombobox itemselector must show output as below.
and if "test2" consists of ids 6090,5040. on selecting test2 in firstcombobox itemselector must show output as below.
problem is. for first time if i select "test1" from firstcombobox , output will come as expected. if i reselect "test2" from firstcombobox , output will come as below.
as you can see, previous data displayed (marked in red rectagle) remains there itself with new data displayed (marked with green rectangle).
I want for every reselection of first combobox, previously displayed data in itemselector to be erased before printing new data on itemselector.
How can I reset the itemselector for every reselection of first combobox?
You should remove all items from the store of the itemselector by the removeAll command. After that you should load the store of the itemselector.
itemselector.store.removeAll();
itemselector.store.load();
Any solutions above solve my problem.
i found solution from Sencha Forum.
https://www.sencha.com/forum/showthread.php?142276-closed-Ext-JS-4.0.2a-itemselector-not-reloading-the-store
in the itemselector.js file, change the line marked below.
populateFromStore: function(store) {
var fromStore = this.fromField.store;
// Flag set when the fromStore has been loaded
this.fromStorePopulated = true;
// THIS LINE BELOW MUST BE CHANGED!!!!!!!!!!!!
fromStore.loadData(store.getRange()); //fromStore.add(store.getRange());
// setValue waits for the from Store to be loaded
fromStore.fireEvent('load', fromStore);
},
You need to insert...
this.reset();
at the head of the function that is inserting the data.
As an example...
Ext.override( Ext.ux.ItemSelector, {
setValue: function(val) {
this.reset();
if (!val) return;
val = val instanceof Array ? val : val.split(this.delimiter);
var rec, i, id;
for (i = 0; i < val.length; i++) {
var vf = this.fromMultiselect.valueField;
id = val[i];
idx = this.toMultiselect.view.store.findBy(function(record){
return record.data[vf] == id;
});
if (idx != -1) continue;
idx = this.fromMultiselect.view.store.findBy(function(record){
return record.data[vf] == id;
});
rec = this.fromMultiselect.view.store.getAt(idx);
if (rec) {
this.toMultiselect.view.store.add(rec);
this.fromMultiselect.view.store.remove(rec);
}
}
}
});
are u got it?
when u select that combobox frist stoe of item selector is null after store load with ur pass the para meters
for example
store.load(null),
store.proxey.url='jso.php?id='+combobox.getrawvalue(),
store.load();
like that so when ur select a value in ur combobox that time ur used a listeners to ur combobox in that listners ur used above code , select ur some value in combobox that time frist store is get null after ur pass some values to json.php then store load with responce so that time old data is remove and new data load in that store
if u post ur code i will give correct code
I ran into the same issue with ExtJS 4.2.1. I got it to work by calling reload() on the data store and then setValue() with an empty string on the item selector in the data store's reload() callback.
Ext.create("Ext.form.field.ComboBox", {
// Other properties removed for brevity
listeners: {
change: function(field, newValue, oldValue, eOpts) {
Ext.getStore("ExampleStore").reload({
callback: function() {
Ext.getCmp("ExampleItemSelector").setValue("");
}
});
}
}
});
Ext.create("Ext.data.Store", {
storeId: "ExampleStore",
// Other properties removed for brevity
});
Ext.create("Ext.form.FormPanel", {
// Other properties removed for brevity
items:[{
xtype: "itemselector",
id: "ExampleItemSelector",
// Other properties removed for brevity
}]
});
For any folks that are curious, I'm fairly convinced there's a bug in the item selector's populateFromStore() function. When the function is called, it blindly adds all of the values from the bound store (store) to the internal store (fromStore). I suspect there should be a call to fromStore.removeAll() prior to the call to fromStore.add(). Here's the relevant code from ItemSelector.js.
populateFromStore: function(store) {
var fromStore = this.fromField.store;
// Flag set when the fromStore has been loaded
this.fromStorePopulated = true;
fromStore.add(store.getRange());
// setValue waits for the from Store to be loaded
fromStore.fireEvent('load', fromStore);
},
EDIT 12/18/2013
If you've configured any callback events on the item selector (e.g. change), you may want to disable the events temporarily when you call setValue(""). For example:
var selector = Ext.getCmp("ExampleItemSelector");
selector.suspendEvents();
selector.setValue("");
selector.resumeEvents();
I had the same problem and finally I decided to modify the extjs source code, not considering it a big issue as extjs itself its saying in the start of the file
Note that this control will most likely remain as an example, and not as a core Ext form
control. However, the API will be changing in a future release and so should not yet be
treated as a final, stable API at this time.
Based on that, as jstricker guessed (and sadly I didn't read and took me a while to arrive to the same conclusion), adding fromStore.removeAll() before fromStore.add() solves the problem.
Outside of the problem (but I think it can be interesting as well), additionally, I also added listConfig: me.listConfig in the MultiSelect configuration (inside createList), that way it's possible to format each item additional options (such as images, etc.) setting in the 'itemselector' the option listConfig as it's explained in the (irrealistic) documentation.
Need to reset the store used in ItemSelector that can be done by setting Empty object like below. Also need to call clearValue() method of ItemSelector component.
store.setData({});
ItemSelectorComponent.clearValue();