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
Related
I have a function that runs every time your location changes and I'm trying to set a value in my array when a certain if statement is found true. All I seem to be doing is removing everything from my variable except the value that I am changing. Bad explanation so here is some code...
The data starts like this:
this.state = { selectedItem: [] }
And will change to something like this during normal app use:
selectedItem: [{address: 'Somewhere', latitude: -37.826835, longitude: 144.992030, found: false }]
Here is where I am trying to change the data (This will always run after some data is added):
const newSelectedItem = () => {
let copyB = {...this.state.selectedItem};
copyB.found = true;
return copyB;
};
this.setState({selectedItem: newSelectedItem});
When I try to run:
{this.state.selectedItem.address}
I see the initial value which would be the address "Somewhere" but when my function runs based on location change it disappears. What have I actually done to my data in my above function?
Have I just made it selectedItem: [{found:true}] or something dumb like that?
You were doing fine except for one single thing , in this function :
const newSelectedItem = () => {
let copyB = {...this.state.selectedItem};
copyB.found = true;
return copyB;
};
this.setState({selectedItem: newSelectedItem});
here copyB is now an object , but yours selected Item was an array. So the problem is now selectedItem is now an object when you do setState with newSelctedItem.
SO copyB.found = true; wouldnt evaluate anything rather, try copyB[0].found = true; so there the value will be accessed and return true accordingly.
And when you try to access the state , replace {this.state.selectedItem.address}
with {this.state.selectedItem[0].address} ,
Hope i helps. feel free to ask any doubts.
You can play around with this pen codepen
Well, this is pretty weird since you still get the initial value. There are some problems in your code:
You want to store your variables in a array: It's fine, but the problem comes from the way you retrieve and set your value. Since selectedItem, your {this.state.selectedItem.address} because this is an object destructuring. To do that, you have to destruct your array first, e.g: item = selectedItem[0] or using map, etc... After that, you can try: item.address.
Another problem is from your newSelectedItem. Since let copyB = {...this.state.selectedItem}; will destruct your selectedItem, take all its properties and set to newSelectedItem, it will make your selectedItem become an object, not an array anymore.
If your selectedItem stores only 1 object, so don't use array. This selectedItem will become:
selectedItem: {
address: 'Somewhere',
latitude: -37.826835,
longitude: 144.992030,
found: false
}
It looks like a JSON object, hence you can do: selectedItem.address
In case you still don't get it, place a little debug or a console.log("selectedItem", this.state.selectedItem) to see what happend, and you will find out.
I think in the end I was mapping a function to the data and not the data itself. This was my eventual solution...
const newMyWaypoinys = this.state.myWaypoints.map(a => {
let copyA = {...a};
if (copyA.address === wp.address) {
if (copyA.address === this.state.selectedItem.address) {
this.setState(prevState => ({
selectedItem: {
...prevState.selectedItem,
found: true
}
}))
}
copyA.found = true;
}
return copyA;
});
this.setState({
myWaypoints: newMyWaypoinys,
});
Question: How can I add pagination (infinite scroll) to my binded Firestore VuexFire reference without re-querying previously retrieved (and binded) results?
Background:
I am currently using VuexFire firestore binding to fill a timeline for most upvoted posts, as an action, in my Vuex store like this:
fillTimeLine: firebaseAction(
({ bindFirebaseRef }) => {
bindFirebaseRef(
'timelineResults',
db
.collection('POSTS')
.orderBy('combined_vote_score', 'desc')
.limit(30)
)
})
This will retrieve the top 30 highest rated posts in my firestore database to my vuex state variable timelineResults.
To add pagination I have found a non-VuexFire example like this:
How to paginate or infinite scroll by number of items in firestore?
var first = db.collection("....").orderBy("price", "desc").limitTo(20);
return first.get().then(function (documentSnapshots) {
// Get the last visible document
var lastVisible = documentSnapshots.docs[documentSnapshots.docs.length-1];
console.log("last", lastVisible);
// Construct a new query starting at this document,
// get the next 25 cities.
var next = db.collection("....")
.orderBy("price", "desc")
.startAfter(lastVisible)
.limit(20);
});
Is there a way to combine the two examples and append results to a binded reference?
You could create a more generic action, just like this:
bindRef: firestoreAction(({ bindFirestoreRef }, { name, ref }) => {
bindFirestoreRef(name, ref);
}),
And then using it like:
this.bindRef({
name: 'timelineResults',
ref: db
.collection('POSTS')
.orderBy('combined_vote_score', 'desc')
.limit(30),
});
There you can change the ref according to your needs. In this case, when you detect the scroll limit:
// lastVisible: using the array position from the previous binding
// since with vuex's bound data you cannot get the snapshots
this.bindRef({
name: 'timelineResults',
ref: db
.collection('POSTS')
.orderBy('combined_vote_score', 'desc')
.startAfter(lastVisible)
.limit(20),
});
I have an array of objects. When my api executes the update method and saves, I'm am broadcasting an event through laravel-echo-server and attempting to mutate state with the update object. I'm trying for real-time updates. Everything but the actual mutation is going according to plan. Here is the beginning of it:
updateExample (state, example) {
state.examples.map(e => {
if (e.id === example.id) {
// entirely replace the old object with the new
}
return false
})
},
What is an ideal way to do this? I could also pop the old object out of the array and push a new one, but that seems wonky.
Nevermind, figured it out:
updateExample (state, example) {
let index = state.examples.findIndex(e => e.id === example.id)
if (index !== -1) {
state.examples[index] = new Example(example)
return
}
state.examples.push(new Example(example))
}
Thanks for looking at it!
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.
In one of my projects I use a dijit.Tree control. I need to add a search to the tree and show only those nodes/leafs which have the searched term in them. However I can't seem to figure out how that can be achieved. Could anybody please help me?
im not entirely sure that your question entirely but it should give hint whereas to go.
Lets use reference documentation example as offset, there is 1) a store 2) a model and 3) the tree
var store = new ItemFileReadStore({
url: "{{dataUrl}}/dijit/tests/_data/countries.json"
});
var treeModel = new ForestStoreModel({
store: store,
query: {"type": "continent"}, // note, this bit
rootId: "root",
rootLabel: "Continents",
childrenAttrs: ["children"]
});
new Tree({
model: treeModel
}, "treeOne");
Interpret the above as such; You have loaded all known countries and continents but 'user' has selected only to show continents by using query on the model - and the hierachy is then represented in a tree structure.
You want a textbox with searching capeabilities, so we hook into onChange
new dijit.form.TextBox({
onChange: function() {
...
}
});
First bit, getting variables
var searchByName = this.get("value");
var oQuery = treeModel.query;
Next, set a new query on the model - preserving the old ones with an object mixin
treeModel.query = dojo.mixin(oQuery, { name: '*'+searchByName+'*' });
Last, notify the model and its tree that changes has occurred - and requery the visible items.
treeModel._requeryTop();
NB If the top-level item (for ForestModel) is not visible, none of its child elements will show, even if the search-string matches those. (Examplewise, Alabama is not shown if US Continent is not matched by query)
EDIT
As OP has the agenda to go by the 'NB', this may not fit needs 100% but its what dojo offers with dijit.Tree.. As it will get rather a lengthy process to recode the model/store queries to include parentbranches up until root i will not do this here - but there are a few tricks still ;)
var tree = new dijit.Tree( {
/**
* Since TreeNode has a getParent() method, this abstraction could be useful
* It sets the dijit.reqistry id into the item-data, so one l8r can get parent items
* which otherwise only can be found by iterating everything in store, looking for item in the parent.children
*
*/
onLoad : function() {
this.forAllNodes(function(node) {
// TreeNode.item <-- > store.items hookup
node.item._NID = node.domNode.id
});
},
/* recursive iteration over TreeNode's
* Carefull, not to make (too many) recursive calls in the callback function..
* fun_ptr : function(TreeNode) { ... }
*/
forAllNodes : function(parentTreeNode, fun_ptr) {
parentTreeNode.getChildren().forEach(function(n) {
fun_ptr(n);
if(n.item.children) {
n.tree.forAllNodes(fun_ptr);
}
})
}
});
(non-tested, but might just work) Example:
// var 'tree' is your tree, extended with
tree.forAllNodes = function(parentTreeNode, fun_ptr) {
parentTreeNode.getChildren().forEach(function(n) {
fun_ptr(n);
if(n.item.children) {
n.tree.forAllNodes(fun_ptr);
}
})
};
// before anything, but the 'match-all' query, run this once
tree.forAllNodes(tree.rootNode, function(node) {
// TreeNode.item <-- > store.items hookup
node.item._NID = node.domNode.id
});
// hopefully, this in end contains top-level items
var branchesToShow = []
// run fetch every search (TextBox.onChange) with value in query
tree.model.store.fetch(query:{name:'Abc*'}, onComplete(function(items) {
var TreeNode = null;
dojo.forEach(items, function(item) {
TreeNode = dijit.byId(item._NID+'');
while(TreeNode.getParent()
&& typeof TreeNode.getParent().item._RI == 'undefined') {
TreeNode = TreeNode.getParent();
}
branchesToShow.push(TreeNode.item);
});
}});
// Now... If a success, try updating the model via following
tree.model.onChildrenChange(tree.model.root, branchesToShow);