How to set a counter for duplicate values in React? - javascript

My code is basically a form with a text input and a submit button. Each time the user input data, my code adds it to an array and shows it under the form.
It is working fine; however, when I add duplicate values, it still adds it to the list. I want my code to count these duplicates and show them next to each input.
For example, if I input two "Hello" and one "Hi" I want my result to be like this:
2 Hello
1 Hi
Here is my code
import React from 'react';
import ShoppingItem from './ShoppingItem';
class ShoppingList extends React.Component {
constructor (props){
super(props);
this.state ={
shoppingCart: [],
newItem :'',
counter: 0 };
}
handleChange =(e) =>
{
this.setState ({newItem: e.target.value });
}
handleSubmit = (e) =>
{
e.preventDefault();
let newList;
let myItem ={
name: this.state.newItem,
id:Date.now()
}
if(!this.state.shoppingCart.includes(myItem.name))
{
newList = this.state.shoppingCart.concat(myItem);
}
if (this.state.newItem !=='')
{
this.setState(
{
shoppingCart: newList
}
);
}
this.state.newItem ="" ;
}
the rest of my code is like this:
render(){
return(
<div className = "App">
<form onSubmit = {this.handleSubmit}>
<h6>Add New Item</h6>
<input type = "text" value = {this.state.newItem} onChange ={this.handleChange}/>
<button type = "submit">Add to Shopping list</button>
</form>
<ul>
{this.state.shoppingCart.map(item =>(
<ShoppingItem item={item} key={item.id} />
)
)}
</ul>
</div>
);
}
}
export default ShoppingList;

Issues
this.state.shoppingCart is an array of objects, so this.state.shoppingCart.includes(myItem.name) will always return false as it won't find a value that is a string.
this.state.newItem = ""; is a state mutation
Solution
Check the newItem state first, if empty then return early
Search this.state.shoppingCart for the index of the first matching item by name property
If found then you want to map the cart to a new array and then also copy the item into a new object reference and update the quantity.
If not found then copy the array and append a new object to the end with an initial quantity 1 property.
Update the shopping cart and newItem state.
Code
handleSubmit = (e) => {
e.preventDefault();
if (!this.state.newItem) return;
let newList;
const itemIndex = this.state.shoppingCart.findIndex(
(item) => item.name === this.state.newItem
);
if (itemIndex !== -1) {
newList = this.state.shoppingCart.map((item, index) =>
index === itemIndex
? {
...item,
quantity: item.quantity + 1
}
: item
);
} else {
newList = [
...this.state.shoppingCart,
{
name: this.state.newItem,
id: Date.now(),
quantity: 1
}
];
}
this.setState({
shoppingCart: newList,
newItem: ""
});
};
Note: Remember to use item.name and item.quantity in your ShoppingItem component.

Replace your "handleSubmit" with below one and check
handleSubmit = (e) => {
e.preventDefault();
const { shoppingCart, newItem } = this.state;
const isInCart = shoppingCart.some(({ itemName }) => itemName === newItem);
let updatedCart = [];
let numberOfSameItem = 1;
if (!isInCart && newItem) {
updatedCart = [
...shoppingCart,
{
name: `${numberOfSameItem} ${newItem}`,
id: Date.now(),
itemName: newItem,
counter: numberOfSameItem
}
];
} else if (isInCart && newItem) {
updatedCart = shoppingCart.map((item) => {
const { itemName, counter } = item;
if (itemName === newItem) {
numberOfSameItem = counter + 1;
return {
...item,
name: `${numberOfSameItem} ${itemName}`,
itemName,
counter: numberOfSameItem
};
}
return item;
});
}
this.setState({
shoppingCart: updatedCart,
newItem: ""
});
};

Related

Updating state when state is an array of objects. React

I'm working on a shopping cart in react by using context. My problem is with changing the state that has an array of objects.
My array will look like this [{itemId: 'ps-5', qty:4}, {itemId: 'iphone-xr', qty:2}]
Here is my code check the comment
export const CartContext = createContext()
class CartContextProvider extends Component {
state = {
productsToPurchase: []
}
addProduct = (itemId)=> {
if (JSON.stringify(this.state.productsToPurchase).includes(itemId)){
// Add one to the qty of the product
this.state.productsToPurchase.map(product=>{
if (product.itemId === itemId){
// This is wrong I have to use setState(), but the syntax is a little bit complex
product.qty = product.qty + 1
}
})
}
else {
this.state.productsToPurchase.push({itemId: itemId, qty: 1})
}
}
render() {
return (
<CartContext.Provider value={{...this.state, addProduct: this.addProduct}}>
{this.props.children}
</CartContext.Provider>
)
}
}
export default CartContextProvider;
You are updating the state directly, but you have to use this.setState to update it,
Live Demo
addProduct = (itemId) => {
this.setState((oldState) => {
const objWithIdExist = oldState.productsToPurchase.find((o) => o.itemId === itemId);
return {
productsToPurchase: !objWithIdExist
? [...oldState.productsToPurchase, { itemId, qty: 1 }]
: oldState.productsToPurchase.map((o) =>
o.itemId !== itemId ? o : { ...o, qty: o.qty + 1 }
)
};
});
};

I want to push an item in a list if its not there . If the item already in the list then remove that item

I want to push an item to the list if its not previously included there. If its there then remove that item. I am able to do the first part, but no idea about how to remove that.
handleCityCheckbox = (param1) => {
var { cityList = [] } = this.state;
//if cityList doesnt have param1
if (!cityList.includes(param1)) {
cityList.push(param1);
this.setState({ cityList });
} else {
}
this.setState({ cityList });
};
what will be the else part?
handleCityCheckbox = (param1) => {
const { cityList = [] } = this.state;
const itemIndex = cityList.indexOf(param1);
if (itemIndex === -1)) {
cityList.push(param1);
} else {
cityList = cityList.filter((e, index) => index !== itemIndex)
}
this.setState({ cityList });
};
Finished App:
Filtering function:
const handleSubmit = (event) => {
event.preventDefault();
if (!name) {
alert("Enter the city name");
return;
}
let tempList = cities.filter(
(city) => city.toLowerCase() !== name.toLowerCase()
);
if (tempList.length === cities.length) {
tempList.push(name);
setCities(tempList);
return;
} else {
setCities(tempList);
}
};
In the above function, we will, first of all, use filter function to filter out i.e. delete the city name if it exists and assign it to tempList, then we compare the size of tempList with main cities list, if it's same then it indicates that the city name was not present in the main list so we will push that name to tempList and update the cities state with modified tempList, else, we just set the filtered out tempList.
Full Example :
import React, { useState } from "react";
import "./styles.css";
export default function App() {
const [cities, setCities] = useState(["Pune", "Delhi"]);
const [name, setName] = useState("");
const handleSubmit = (event) => {
event.preventDefault();
if (!name) {
alert("Enter the city name");
return;
}
let tempList = cities.filter(
(city) => city.toLowerCase() !== name.toLowerCase()
);
if (tempList.length === cities.length) {
tempList.push(name);
setCities(tempList);
return;
} else {
setCities(tempList);
}
};
return (
<div className="App">
<form onSubmit={handleSubmit}>
<input onChange={(event) => setName(event.target.value)} />
<button type="submit">Submit</button>
</form>
{cities.map((city) => (
<p>{city}</p>
))}
</div>
);
}
Codesandbox Link

how to add multiple objects in reactjs?

I want to add new Objects when user click on checkbox. For example , When user click on group , it will store data {permission:{group:["1","2"]}}. If I click on topgroup , it will store new objects with previous one
{permission:{group:["1","2"]},{topGroup:["1","2"]}}.
1st : The problem is that I can not merge new object with previous one . I saw only one objects each time when I click on the group or topgroup.
onChange = value => checked => {
this.setState({ checked }, () => {
this.setState(prevState => {
Object.assign(prevState.permission, { [value]: this.state.checked });
});
});
};
<CheckboxGroup
options={options}
value={checked}
onChange={this.onChange(this.props.label)}
/>
Here is my codesanbox:https://codesandbox.io/s/stackoverflow-a-60764570-3982562-v1-0qh67
It is a lot of code because I've added set and get to set and get state. Now you can store the path to the state in permissionsKey and topGroupKey. You can put get and set in a separate lib.js.
In this example Row is pretty much stateless and App holds it's state, this way App can do something with the values once the user is finished checking/unchecking what it needs.
const Checkbox = antd.Checkbox;
const CheckboxGroup = Checkbox.Group;
class Row extends React.Component {
isAllChecked = () => {
const { options, checked } = this.props;
return checked.length === options.length;
};
isIndeterminate = () => {
const { options, checked } = this.props;
return (
checked.length > 0 && checked.length < options.length
);
};
render() {
const {
options,
checked,
onChange,
onToggleAll,
stateKey,
label,
} = this.props; //all data and behaviour is passed by App
return (
<div>
<div className="site-checkbox-all-wrapper">
<Checkbox
indeterminate={this.isIndeterminate()}
onChange={e =>
onToggleAll(e.target.checked, stateKey)
}
checked={this.isAllChecked()}
>
Check all {label}
</Checkbox>
<CheckboxGroup
options={options}
value={checked}
onChange={val => {
onChange(stateKey, val);
}}
/>
</div>
</div>
);
}
}
//helper from https://gist.github.com/amsterdamharu/659bb39912096e74ba1c8c676948d5d9
const REMOVE = () => REMOVE;
const get = (object, path, defaultValue) => {
const recur = (current, path) => {
if (current === undefined) {
return defaultValue;
}
if (path.length === 0) {
return current;
}
return recur(current[path[0]], path.slice(1));
};
return recur(object, path);
};
const set = (object, path, callback) => {
const setKey = (current, key, value) => {
if (Array.isArray(current)) {
return value === REMOVE
? current.filter((_, i) => key !== i)
: current.map((c, i) => (i === key ? value : c));
}
return value === REMOVE
? Object.entries(current).reduce((result, [k, v]) => {
if (k !== key) {
result[k] = v;
}
return result;
}, {})
: { ...current, [key]: value };
};
const recur = (current, path) => {
if (path.length === 1) {
return setKey(
current,
path[0],
callback(current[path[0]])
);
}
return setKey(
current,
path[0],
recur(current[path[0]], path.slice(1))
);
};
return recur(object, path, callback);
};
class App extends React.Component {
state = {
permission: { group: [] },
topGroup: [],
some: { other: [{ nested: { state: [] } }] },
};
permissionsKey = ['permission', 'group']; //where to find permissions in state
topGroupKey = ['topGroup']; //where to find top group in state
someKey = ['some', 'other', 0, 'nested', 'state']; //where other group is in state
onChange = (key, value) => {
//use set helper to set state
this.setState(set(this.state, key, arr => value));
};
isIndeterminate = () =>
!this.isEverythingChecked() &&
[
this.permissionsKey,
this.topGroupKey,
this.someKey,
].reduce(
(result, key) =>
result || get(this.state, key).length,
false
);
toggleEveryting = e => {
const checked = e.target.checked;
this.setState(
[
this.permissionsKey,
this.topGroupKey,
this.someKey,
].reduce(
(result, key) =>
set(result, key, () =>
checked
? this.plainOptions.map(({ value }) => value)
: []
),
this.state
)
);
};
onToggleAll = (checked, key) => {
this.setState(
//use set helper to set state
set(this.state, key, () =>
checked
? this.plainOptions.map(({ value }) => value)
: []
)
);
};
isEverythingChecked = () =>
[
this.permissionsKey,
this.topGroupKey,
this.someKey,
].reduce(
(result, key) =>
result &&
get(this.state, key).length ===
this.plainOptions.length,
true
);
plainOptions = [
{ value: 1, name: 'Apple' },
{ value: 2, name: 'Pear' },
{ value: 3, name: 'Orange' },
];
render() {
return (
<React.Fragment>
<h1>App state</h1>
{JSON.stringify(this.state)}
<div>
<Checkbox
indeterminate={this.isIndeterminate()}
onChange={this.toggleEveryting}
checked={this.isEverythingChecked()}
>
Toggle everything
</Checkbox>
</div>
{[
{ label: 'group', stateKey: this.permissionsKey },
{ label: 'top', stateKey: this.topGroupKey },
{ label: 'other', stateKey: this.someKey },
].map(({ label, stateKey }) => (
<Row
key={label}
options={this.plainOptions}
// use getter to get state selected value
// for this particular group
checked={get(this.state, stateKey)}
label={label}
onChange={this.onChange} //change behaviour from App
onToggleAll={this.onToggleAll} //toggle all from App
//state key to indicate what state needs to change
// used in setState in App and passed to set helper
stateKey={stateKey}
/>
))}
</React.Fragment>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<link href="https://cdnjs.cloudflare.com/ajax/libs/antd/4.0.3/antd.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/antd/4.0.3/antd.js"></script>
<div id="root"></div>
I rewrite all the handlers.
The bug in your code is located on the usage of antd Checkbox.Group component with map as a child component, perhaps we need some key to distinguish each of the Row. Simply put them in one component works without that strange state update.
As the demand during communication, the total button is also added.
And, we don't need many states, keep the single-source data is always the best practice.
import React from "react";
import ReactDOM from "react-dom";
import "antd/dist/antd.css";
import "./index.css";
import { Checkbox } from "antd";
const group = ["group", "top"];
const groupItems = ["Apple", "Pear", "Orange"];
const CheckboxGroup = Checkbox.Group;
class App extends React.Component {
constructor() {
super();
this.state = {
permission: {}
};
}
UNSAFE_componentWillMount() {
this.setDefault(false);
}
setDefault = fill => {
const temp = {};
group.forEach(x => (temp[x] = fill ? groupItems : []));
this.setState({ permission: temp });
};
checkLength = () => {
const { permission } = this.state;
let sum = 0;
Object.keys(permission).forEach(x => (sum += permission[x].length));
return sum;
};
/**
* For total
*/
isTotalIndeterminate = () => {
const len = this.checkLength();
return len > 0 && len < groupItems.length * group.length;
};
onCheckTotalChange = () => e => {
this.setDefault(e.target.checked);
};
isTotalChecked = () => {
return this.checkLength() === groupItems.length * group.length;
};
/**
* For each group
*/
isIndeterminate = label => {
const { permission } = this.state;
return (
permission[label].length > 0 &&
permission[label].length < groupItems.length
);
};
onCheckAllChange = label => e => {
const { permission } = this.state;
const list = e.target.checked ? groupItems : [];
this.setState({ permission: { ...permission, [label]: list } });
};
isAllChecked = label => {
const { permission } = this.state;
return !groupItems.some(x => !permission[label].includes(x));
};
/**
* For each item
*/
isChecked = label => {
const { permission } = this.state;
return permission[label];
};
onChange = label => e => {
const { permission } = this.state;
this.setState({ permission: { ...permission, [label]: e } });
};
render() {
const { permission } = this.state;
console.log(permission);
return (
<React.Fragment>
<Checkbox
indeterminate={this.isTotalIndeterminate()}
onChange={this.onCheckTotalChange()}
checked={this.isTotalChecked()}
>
Check all
</Checkbox>
{group.map(label => (
<div key={label}>
<div className="site-checkbox-all-wrapper">
<Checkbox
indeterminate={this.isIndeterminate(label)}
onChange={this.onCheckAllChange(label)}
checked={this.isAllChecked(label)}
>
Check all
</Checkbox>
<CheckboxGroup
options={groupItems}
value={this.isChecked(label)}
onChange={this.onChange(label)}
/>
</div>
</div>
))}
</React.Fragment>
);
}
}
ReactDOM.render(<App />, document.getElementById("container"));
Try it online:
Please try this,
onChange = value => checked => {
this.setState({ checked }, () => {
this.setState(prevState => {
permission : { ...prevSatate.permission , { [value]: this.state.checked }}
});
});
};
by using spread operator you can stop mutating the object. same way you can also use object.assign like this.
this.setState(prevState => {
permission : Object.assign({} , prevState.permission, { [value]: this.state.checked });
});
And also i would suggest not to call setState in a callback. If you want to access the current state you can simply use the current checked value which you are getting in the function itself.
so your function becomes ,
onChange = value => checked => {
this.setState({ checked });
this.setState(prevState => {return { permission : { ...prevSatate.permission, { [value]: checked }}
}});
};
Try the following
//Inside constructor do the following
this.state = {checkState:[]}
this.setChecked = this.setChecked.bind(this);
//this.setChecked2 = this.setChecked2.bind(this);
//Outside constructor but before render()
setChecked(e){
this.setState({
checkState : this.state.checkState.concat([{checked: e.target.id + '=>' + e.target.value}])
//Id is the id property for a specific(target) field
});
}
//Finally attack the method above.i.e. this.setChecked to a form input.
Hope it will address your issues

Remove item when unchecked checkbox

I have checkbox with a couple item in it, when i click the check box the item will add to state called currentDevice, but when i unchecked the item it keep add item and not remove it.
How do i remove item from state when i unchecked the box. Im using react-native-element checkbox. Thankyou before
The code:
constructor(props) {
super(props)
this.state = {
currentDevice: [],
checked: []
}
}
handleChange = (index, item) => {
let checked = [...this.state.checked];
checked[index] = !checked[index];
this.setState({ checked });
this.setState({currentDevice: [...this.state.currentDevice, item.bcakId]})
}
renderFlatListDevices = (item, index) => {
return (
<ScrollView>
<CheckBox
title={item.label || item.bcakId}
checked={this.state.checked[index]}
onPress={() => {this.handleChange(index, item)}}
checkedIcon='dot-circle-o'
uncheckedIcon='circle-o'
checkedColor='#FFE03A'
containerStyle={styles.containerCheckBox}
textStyle={styles.textCheckBox}
/>
</ScrollView>
)
}
change the handleChange method to
const handleChange = (index, item) => {
const {currentDevice, checked} = state;
const found = currentDevice.some((data) => data === item.bcakId);
if (found) {
currentDevice.splice(
currentDevice.findIndex((data) => data === item.bcakId),
1
);
} else {
currentDevice.push(item.bcakId);
}
checked[index] = !checked[index];
this.setState({
currentDevice,
checked,
})
};
I found the solution, here is the code :
handleChange = (index, item) => {
let checked = [...this.state.checked];
checked[index] = !checked[index];
this.setState({ checked });
this.setState(previous => {
let currentDevice = previous.currentDevice;
let index = currentDevice.indexOf(item.bcakId)
if (index === -1) {
currentDevice.push(item.bcakId)
} else {
currentDevice.splice(index, 1)
}
return { currentDevice }
}, () => console.log(this.state.currentDevice));
}
Credit : Adding checked checkboxes to an array and removing the unchecked ones - react native

How come I cannot get this item marked as 'complete' (react to do list)?

I cannot get the completed property in the todo item object to change from false to true (in the completeItem handler function).
Essentially, when the user clicks on the checkbox, it fires the completeItem handler, where the id of the object is passed as an argument.
class App extends Component {
state = {
input: "",
todos: []
};
onChangeHandler = e => {
this.setState({ input: e.target.value });
};
onSubmitHandler = () => {
const toDos = [...this.state.todos];
const id = toDos.length ? toDos[toDos.length - 1].id + 1 : 1;
toDos.push({
id,
description: this.state.input,
completed: false
});
this.setState({ todos: toDos, input: "" });
};
completeItem = (id, checked) => {
let todos = [...this.state.todos];
todos = todos.map((item, index) => {
if (item.id === id) {
const completed = !item.completed;
console.log(completed);
return {
id: item.id,
completed,
description: item.description
};
}
return item;
});
this.setState({ todos });
console.log(this.state.todos);
};
render() {
return (
<div className="App">
<InputField
change={this.onChangeHandler}
submit={this.onSubmitHandler}
input={this.state.input}
/>
<ToDoList todos={this.state.todos} completeItem={this.completeItem} />
</div>
);
}
}
export default App;
I am wanting to change the todo item's completed property to true.

Categories

Resources