I am using a checkbox to select items.
When the checkbox is checked, item is added to a Set stored in state.
When checkbox is unchecked, item is deleted from the Set stored in state
Upon verifying if item was removed from Set, the function
set.has(setItem)
returns false.
However when checking the size of the Set, it is as if I did not remove anything.
I'm probably not updating the set to account for the deleted item.
Can anyone help?
my handleChange function below...
const handleChange = (e) => {
if (e.target.checked) {
var checkedItemId = parseInt(e.target.value)
setCheckedItems(new Set(checkedItems.add(checkedItemId)))
}else if(!e.target.checked){
checkedItems.delete(checkedItemId)
setCheckedItems(new Set(checkedItems))
}
}
const handleChange = (e) => {
const checkedItemId = parseInt(e.target.value);
if (e.target.checked) {
setCheckedItems(prevState => {
let newSetCheckedItems = new Set(...prevState.checkedItems);
return newSetCheckedItems.add(checkedItemId);
})
} else if (!e.target.checked) {
setCheckedItems(prevState => {
let newSetCheckedItems = new Set(...prevState.checkedItems);
return newSetCheckedItems.detele(checkedItemId);
});
}
}
The solution was to use a deep copy of the set.
Do the adding/removing operations on that deep copy.
Then update the set with the deep copy.
//i don't know about the react but i can give you solution in the jquery.
var new_arr=[];
$(document.body).on('click','.class_name',function(){
var is_checked = $(this).is(':checked');
var value_check_box = $(this).val();
if(is_checked==true){
// push the value of the check box in the array variable
new_arr.push(value_check_box);
}else{
var removeItem = value_check_box // it is the value when the user uncheck the uncheckbox;
new_arr = jQuery.grep(new_arr, function(value) {
return value != removeItem;
});
};
})
Related
I wants to prevent addition of duplicate items to cart. I have tried the code below but it's working only for single item, when there are multiple items in the cart the duplicate items are allowed to add in to the cart. Here is my code
addToCart = (id) => {
let item = this.getItem(id);
if ([...this.state.cart]) {
[...this.state.cart].map((i) => {
if (i.product_name == item.product_name) {
alert("Item is already in cart");
} else {
this.setState((this.state.cart = [...this.state.cart, item]));
}
});
} else {
this.setState((this.state.cart = [...this.state.cart, item]));
}
console.log(this.state.cart);
};
You need to use map only to check if the item already exists, and then either add it or alert that the item is repeated.
One way of doing it would be like this:
existing = [...this.state.cart].map((i) => {
if (i.product_name == item.product_name) {
return i;
}
});
if (existing) {
alert("Item is already in cart");
} else {
this.setState((this.state.cart = [...this.state.cart, item]));
}
Explanation
map function executes the code for each of the items in the collection, which means the moment it finds an item in the cart different from the item selected, it will add the item selected.
So let's say your cart has [apple, orange] and you want to add apple again. When the map code executes it first looks like this:
if ("apple" == "apple") {
alert("Item is already in cart");
} else {
this.setState((this.state.cart = [...this.state.cart, apple]));
}
It doesn't add the item because it already exists... but then it executes a second time, and it looks like this:
if ("orange" == "apple") {
alert("Item is already in cart");
} else {
this.setState((this.state.cart = [...this.state.cart, apple]));
}
It gets added because the second item is different.
What the new code does is that it returns a value only if the item exists and, after looping throuhght all the items in the cart, it checks that value and adds the item if it is nothing.
An item should be added to the cart, if the latter doesn't contain it already.
To check if an Array contains an object, that fulfills a certain condition use the some method, as said by #Isaac Batista.
On the other hand, when you want to update state, by using it's previous value, you should use the callback argument of setState.
See https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous
this.setState((state)=>{
// if cart already contains item
if(state.cart.some(itm=>itm.product_name == item.product_name)) {
return {}; // do not update state
} else {
return {cart: state.cart.concat(item)}; // add item to cart
}
}
You can use filter method to check whether the item is already available or not. With this you can also avoid the nested if condition also.
addToCart = (id) => {
let item = this.getItem(id);
let checkCart = [...this.state.cart].filter((i) => {
return i.product_name == item.product_name;
});
if (checkCart.length !== 0) {
alert("Item is already in cart");
} else {
this.setState((this.state.cart = [...this.state.cart, item]));
}
console.log(this.state.cart);
};
Here is a functional exemple, but note some points:
You are mutating state, and you should not do it, as it is explained here. So, you should just call setState passing the new value via argument, like this: this.setState(newValue).
A map is used to create a new array, the correct function to find out if some element passes a rule is some. This will allow you to check if some product inside cart is the clicked product.
// quick example
[1,2,3,4].some(number => number === 2) // true
[1,2,3,4].some(number => number === 5) // false
Finally, i would do something like this
const { cart } = this.state;
const product = this.getItem(id);
// returns true if there is any product with the same id
const isProductInCart = cart.some((item) => item.id === product.id);
if (isProductInCart) {
alert("Product already in cart");
} else {
this.setState({
cart: [...cart, product]
});
}
I am trying to figure out how to do this but can't seem to wrap my head around it..
I have an address object
const obj = {
"address_type":"Home",
"country":"US",
"addressLine1":"123 Any Street",
"addressLine2":"",
"city":"Any Town",
"state":"Indiana",
"state_code":"IN",
"zip":"46220-4466",
"phone":"6715551313",
"mobile_number":"",
"extn":"",
"fax":"",
"county_name":"MyCounty"
}
I want to check for any key that has a value but only specific keys
const objProps = ["addressLine1","addressLine2","city","state_code","zip","county_name"];
I want to check all keys in objProps against my address object and if any one of them contains a value return true (doesn't matter if its 1 or all 6).. If all keys don't contain a value then return false (Sometimes I will get an address object that has all null values)
I've tried various ways to accomplish this but have failed in each one.
The variation I am working on now is using reduce. While it doesn't meet my needs I thought I could check the resulting array and if length was greater than 0 than I have my answer..
Work-in-progress:
function hasAddressData(obj: any) {
const objProps = ["addressLine1","addressLine2","city","state_code","zip","county_name"];
const keysWithData = objProps.reduce((accumulator, key) => {
const propExistsOnObj = obj.hasOwnProperty(key);
let keyHasData = [];
if (obj[key].length > 0 ) {
keyHasData = obj[key]
}
if (!propExistsOnObj) {
accumulator.push(key);
} else if (keyHasData) {
const equalValueKeyIndex = accumulator.indexOf(key);
accumulator.splice(equalValueKeyIndex, 1);
}
return accumulator;
});
return keysWithData;
}
The above is messed up I know and doesn't work.. Just learning this stuff.. anyone have a suggestion or comment?
Check that .some of the objProps, when looked up on the obj, contain a value. (Either with Boolean or by comparing against '')
const obj = {
"address_type":"Home",
"country":"US",
"addressLine1":"123 Any Street",
"addressLine2":"",
"city":"Any Town",
"state":"Indiana",
"state_code":"IN",
"zip":"46220-4466",
"phone":"6715551313",
"mobile_number":"",
"extn":"",
"fax":"",
"county_name":"MyCounty"
}
const objProps = ["addressLine1","addressLine2","city","state_code","zip","county_name"];
const somePopulated = objProps.some(prop => obj[prop]);
// or prop => obj[prop] !== ''
console.log(somePopulated);
const obj = {
"address_type":"Home",
"country":"US",
"addressLine1":"",
"addressLine2":"",
"city":"",
"state":"Indiana",
"state_code":"",
"zip":"",
"phone":"6715551313",
"mobile_number":"",
"extn":"",
"fax":"",
"county_name":""
}
const objProps = ["addressLine1","addressLine2","city","state_code","zip","county_name"];
const somePopulated = objProps.some(prop => obj[prop]);
// or prop => obj[prop] !== ''
console.log(somePopulated);
function checkKeys(target, props) {
return props.some((prop) => {
return target.hasOwnProperty(prop) && target[prop];
});
}
Explanation: some iterates through the props you want to check, returning true immediately when one is found (i.e. the callback returns true). If no props are found (i.e. no callback returns true), some returns false.
hasOwnProperty ensures that you are only checking properties on target, and not looking up the prototype chain. target[prop] checks for a truthy value. You may need to modify this last check if you're going to be handling values other than strings.
I have a map component that contains some clickable overlays on the map. Users can click and unclick the overlays on the map to select them and when they do so the app loads some data based on the overlays that are currently selected.
The current structure is as follows:
User clicks the map which executes a function passed to the map as a prop, which takes the current value of the neighborhoods and either adds or removes them from the query string.
The function executes a history.push()
I use a useEffect checking the value of the query param neighborhood and send a request to the backend to fetch the listings if the values have changed.
My issue is that when the user clicks on the map, the function executes but the value pushed to the params is never updated, causing the logic to fail the next time the user clicks on the map.
Relevant Snippets of Code are as follows:
history/param variables:
const { region, state, neighborhood, transactiontype } = useParams();
const location = useLocation();
const { pathname, search } = location;
Function that is passed down to the child map component:
const updateChildComponentHandler = (dataFromChild, addorRemove) => {
SetLastSetter("map");
SetNeighborhoodTypeSelected("custom");
// if a new neighborhood is added, just taking the existing string and adding &`neighborhood`
if (addorRemove === "add") {
let newGroup = ""
if (neighborhood !== "any") {
newGroup = `${neighborhood}&${dataFromChild}`;
SetMapChangeType("add");
}
// if no neighborhood was initially selected, just replacing the "any" with the proper neighborhood
if (neighborhood === "any") {
SetMapChangeType("add");
newGroup = `${dataFromChild}`
}
// pushing the new parameter string
const newPath = pathname.replace(neighborhood, newGroup);
history.push(`${newPath}${search}`);
}
// same concept as above, just removing the neighborhood from the string if it is removed from the map
if (addorRemove === "remove") {
let newGroup;
if (neighborhood !== dataFromChild) {
newGroup = neighborhood.replace(`&${dataFromChild}`, "")
SetMapChangeType("delete");
}
if (neighborhood === dataFromChild) {
newGroup = "any";
SetMapChangeType("add");
}
if (neighborhood.split("&")[0] === dataFromChild && neighborhood !== dataFromChild) {
newGroup = neighborhood.replace(`${dataFromChild}&`, "")
SetMapChangeType("delete");
}
const newPath = pathname.replace(neighborhood, newGroup);
const newerPath = `${newPath}${search}`;
history.push(newerPath);
deleteListings(dataFromChild);
}
SetUpdateMap(true);
}
UseEffect Logic:
useEffect(() => {
const func = async () => {
let neighborhoodParams;
if (neighborhood !== "any") {
neighborhoodParams = neighborhood.replace("%20", " ").split("&")
}
if (neighborhood === "any") {
neighborhoodParams = [];
neighborhoodParams[0] = "any";
}
const neighborhoods = neighborhood.split("%20").join("_");
SetNeighborhoodParams(neighborhoodParams);
SetNeighborhoodParamString(neighborhoods);
if (mapChangeType === "add") {
if (neighborhoodParams.length > 0) {
if (!requestMulti) {
await fetchProperties(transactiontype, neighborhoodParams[neighborhoodParams.length - 1].split(" ").join("_"), filteredRegion, state, filters, "single")
}
if (requestMulti) {
await fetchProperties(transactiontype, neighborhoods, filteredRegion, state, filters, "multi")
SetRequestMulti(false);
}
}
}
}
func();
}, [neighborhood]);
The issue I am experiencing is that when the map initially loads, the neighborhood is set to "any". The first time a user clicks an overlay for a neighborhood, the correct data is sent from the map and the map/data requests update and the URL parameter up top shows the new neighborhood. However, the second time a user clicks, the value of { neighborhood } is not updated and is still set to "any", so the function just replaces the value of { neighborhood } rather than adding it on as per above. I previously coded this with class components and am trying to convert it to hooks, but it seems like there is some dissonance that is causing the map component not to have access to the updated history variable. I am new to react/hooks, and appreciate if anyone could lend some advice.
Thanks!
Adding a node to a list and yet a component is not re-rendering. Mobx Chrome Extension dev tools says it's a dependency but for some reason still no reaction!
A button renders 'Add' or 'Remove' based on whether a node is in a list. It doesn't re-render unless I move to another component and then open this component again.
Buttons:
#inject("appStore") #observer
class EntityTab extends Component {
...
render() {
return (
...
{/* BUTTONS */}
{ this.props.appStore.repo.canvas.graph.structure.dictionary[id] !== undefined ?
<div onClick={() => this.props.appStore.repo.canvas.graph.function(id)}>
<p>Remove</p>
</div>
:
<div onClick={() => this.props.appStore.currRepo.canvas.otherfunction(id)}>
<p>Add</p>
</div>
}
...
)
}
}
The Add button renders, I click on the button which triggers
this.props.appStore.currRepo.canvas.otherfunction(id)
and then we go to this function
#observable graph = new Graph();
...
#action
otherfunction = (idList) => {
// Do nothing if no ids are given
if (idList === null || (Array.isArray(idList) && idList.length === 0)) return;
// If idList is a single value, wrap it with a list
if (!Array.isArray(idList)) { idList = [idList] }
let nodesToAdd = [];
let linksToAdd = [];
// Add all new links/nodes to graph
Promise.all(idList.map((id) => { return this.getNode(id, 1) }))
.then((responses) => {
for (let i = 0; i < responses.length; i++) {
let res = responses[i];
if (res.success) {
nodesToAdd.push(...res.data.nodes);
linksToAdd.push(...res.data.links);
}
}
this.graph.addData(nodesToAdd, linksToAdd, idList, this.sidebarVisible);
});
};
The getNode function creates new Node objects from the data. For reference, those objects are instantiated as such
export default class Node {
id = '';
name = '';
type = '';
constructor(r) {
for (let property in r) {
// Set Attributes
this[property] = r[property];
}
}
}
anyway, the addToGraphFromIds triggers
this.graph.addData(nodesToAdd, linksToAdd);
and then we go to that function
#action
addData = (nodes, links) => {
this.structure.addNodes(nodes);
this.structure.addLinks(links);
};
which triggers
this.structure.addNodes(nodes);
which leads to this function
#observable map = new Map();
#observable map2 = new Map();
#observable dictionary = {};
#observable dictionary2 = {};
#observable allNodes = [];
#observable allLinks = [];
...
#action
addNodes = (nodes=[]) => {
if (!nodes || nodes.length === 0) return;
nodes = utils.toArray(nodes);
// Only consider each new node if it's not in the graph or a duplicate within the input list
nodes = _.uniqBy(nodes, (obj) => { return obj.id; });
const nodesToConsider = _.differenceBy(nodes, this.allNodes, (obj) => { return obj.id; });
// Add nodes to map
let currNode;
for (let i = 0; i < nodesToConsider.length; i++) {
currNode = nodesToConsider[i];
this.map.set(currNode.id, new Map());
this.map2.set(currNode.id, new Map());
this.dictionary[currNode.id] = currNode;
}
// Update internal list of nodes
this.allNodes = this.allNodes.concat(nodesToConsider);
};
As we can see in the first codebox,
this.props.appStore.repo.canvas.graph.structure.dictionary[id] !== undefined
Should cause the button to change values as we have added the current node. The nodes appear in the dictionary when I log or use mobx chrome extension dev tools, but I have to switch tabs and then the button will re-render. I've tried using other lines like
this.props.appStore.repo.canvas.graph.structure.allNodes.includes(node)
but that doesn't work either. Am absolutely stuck and need help. I have a feeling it has to do with nested observables, and maybe tagging #observable isn't good enough, but not quite sure. repo and canvas are marked as observables and instantiate a new Repo() object and new Canvas() object, much like new Node() is created in getNodes.
Mobx (v4) does not track addition or removal of entries in an observable object unless observable.map is used. Or upgrading to mobx v5 should solve the issue.
For your specific issue you can try:
#observable nodeIdToNodeData = {};
//...
this.nodeIdToNodeData = {...this.nodeIdToNodeData, [currNode.id]: currNode};
Or try to upgrade to mobx v5.
More info here
Looks like Edward solved it, similar to Redux it looks like you have to create a new object with the dictionary rather than modify it. I'm guessing it's because the key currNode.id is already defined (as the value undefined), and we're just modifying it to be currNode. That's my guess, regardless it works now.
So I just spent an hour debugging this code and finally got it to work, but I would want to know why this happened in the first place. I have a function that takes a value from my state, operates on it and saves the output in another variable in the state. This is the fuction:
getFolderNames = async () => {
const promises = this.state.rows.map(async item => {
if (item[".tag"] == "folder" && item.name.length > 20) {
item.name = await getFolderName(item.name);
return item;
} else return item;
});
const result = await Promise.all(promises);
this.setState({
rowsToDisplay: result
});
};
when i run this function, it was updating both the rows and rowsToDisplay to the result variable when i was only calling setState on only one of them.
Changing the function as below solves the issue but I would like to know why.
getFolderNames = async () => {
const promises = this.state.rows.map(async item => {
if (item[".tag"] == "folder" && item.name.length > 20) {
let item2 = {
...item
};
item2.name = await getFolderName(item.name);
return item2;
} else return item;
});
const result = await Promise.all(promises);
this.setState({
rowsToDisplay: result
});
};
It's because of how JavaScript handles variables. When you set a variable to an array or object, it doesn't make a new object but rather just references the original array/object.
As such, if you set a variable to equal some object, and then set a property of that variable, the original object will also be updated. Check this snippet for an example.
var foo = {changed: false};
var bar = foo;
bar.changed = true;
console.log("foo", foo.changed)
console.log("bar", bar.changed)
You can read more about the subject here: https://codeburst.io/explaining-value-vs-reference-in-javascript-647a975e12a0
I hope this helps you in the future, since I know I also spent many hours banging my head against exactly the sort of cases you described in your original question.