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
Related
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: ""
});
};
I have a react-table where I'm generating about 100 checkboxes dynamically. I have tried everything I can think of, but checking/unchecking the checkboxes takes a very long time before I can see any changes. It seems to be re-rendering every single checkbox before I can see a tick on the checkbox I just clicked on (the page freezes for about 5-10 secs). I'm using a change handler defined in a wrapper component passing as a prop.
// Generate columns for table
// this is added to the column props of React-Table
function setActionsColumns() {
return rolesActionsList.map((action, index) => {
const actionName = get(action, 'name');
return {
Header: actionName,
accessor: actionName,
Cell: row => {
const objectName = get(row, 'original.name');
let data = formData.permissions.filter(
perm => row.tdProps.rest.action === perm.action
);
let roleData = data[0];
let checked;
if (get(roleData, 'object') === row.tdProps.rest.object) {
checked = get(roleData, 'checked');
}
return (
<Checkbox
key={`${actionName}_${index}`}
name={actionName}
onChange={onCheckboxChange}
data-object={objectName}
checked={checked}
/>
);
},
};
});
}
// Checkbox handler defined in a class component
handleCheckboxChange = (event: SyntheticEvent<HTMLInputElement>) => {
const { roleData } = this.state;
const target = event.currentTarget;
const actionName = target.name;
const checked = target.checked;
const objectName = target.dataset.object.toUpperCase();
const checkedItem = {
action: actionName,
object: objectName,
checked,
};
let checkedItemsList = [];
let isInArray = roleData.permissions.some(perm => {
return perm.action && perm.object === checkedItem.object;
});
if (!isInArray) {
checkedItemsList = [...roleData.permissions, checkedItem];
} else {
checkedItemsList = roleData.permissions.filter(
perm => perm.action !== checkedItem.action
);
}
this.setState({
roleData: {
...this.state.roleData,
permissions: checkedItemsList,
},
});
};
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
I am using React and Redux in order to manage the state of a list of checkboxes. What I have so far is working but eslint is sending me back an error:
assignment to property of function parameter
So I think I am doing something wrong.
This is the component:
import { toggleCheckboxAction, setCheckboxesToChecked } from '../actions/cancellations';
const CheckboxList = ({
columnsFilterHandler,
setCheckboxesToCheckedHandler,
checkboxes,
t,
}) => {
const onChange = (value, id, event) => {
const item = checkboxes.slice().find(idx => idx.id === id);
if (item) {
item.checked = !item.checked;
columnsFilterHandler(value, id, event.target.value);
return { checkboxes };
}
return item;
};
const setCheckboxesToTrue = () => {
const items = checkboxes.filter(checkbox => checkbox.checked === false);
items.map(item => {
if (item) {
item.checked = !item.checked; // LINE THROWING THE WARNING
setCheckboxesToCheckedHandler(item.checked);
}
return item;
});
};
return (
<>
<ToolbarTitle title="Columns" />
<ToolbarOption>
<Button kind="ghost" small onClick={setCheckboxesToTrue}>
{t('cancellations.resetDefault')}
</Button>
</ToolbarOption>
{checkboxes.map(checkbox => (
<ToolbarOption>
<Checkbox
key={checkbox.id}
id={checkbox.id}
labelText={checkbox.labelText}
value={checkbox.value}
checked={checkbox.checked}
onChange={onChange}
/>
</ToolbarOption>
))}
</>
);
};
CheckboxList.propTypes = {
columnsFilterHandler: PropTypes.func.isRequired,
setCheckboxesToCheckedHandler: PropTypes.func.isRequired,
checkboxes: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
};
const mapStateToProps = state => ({
checkboxes: state.cancellations.checkboxes,
});
export default compose(
connect(
mapStateToProps,
dispatch => ({
columnsFilterHandler: (value, isChecked, valueName) => {
dispatch(toggleCheckboxAction(value, isChecked, valueName));
},
setCheckboxesToCheckedHandler: isChecked => {
dispatch(setCheckboxesToChecked(isChecked));
},
}),
),
)(translate()(CheckboxList));
On the onChange function is where I play with the checkboxes, setting them to checked or !checked.
The issue comes from the function setCheckboxesToTrue on the line item.checked = !item.checked;
This is the action:
const setCheckboxesToChecked = isChecked => ({
type: ActionTypes.CHECKED_ALL_CHECKBOXES,
payload: { isChecked },
});
And this is the reducer to set them true or false:
[ActionTypes.TOGGLE_CHECKBOX](state, action) {
return {
...state,
checkboxes: initialState.checkboxes.map(checkbox => {
if (checkbox.id !== action.payload.id) {
return checkbox;
}
return {
...checkbox,
checked: !checkbox.checked,
};
}),
};
},
And this should be the reducer to set them all to true:
[ActionTypes.CHECKED_ALL_CHECKBOXES](state, action) {
return {
...state,
checkboxes: initialState.checkboxes.map(checkbox => {
if (checkbox.id !== action.payload.id) {
return checkbox;
}
return {
...checkbox,
checked: checkbox.checked,
};
}),
};
},
Do you guys know a better way to do what I am attempting to do or something to fix the error I am getting?
So, ESLint is complaining for exactly the reason it says: you are modifying the value of a function parameter. While this is perfectly valid JS and will work, it's highly discouraged because it makes it very easy to create bugs. Specifically:
items.map(item => {
if (item) {
item.checked = !item.checked; // HERE, you modify `item` which is the parameter passed to the map callback function
setCheckboxesToCheckedHandler(item.checked);
}
return item;
});
What you should do (and have done elsewhere in your code so you might have just forgotten to here):
items.map(item => {
const newItem = { ...item }; // Create a copy of `item` so that you don't need to modify the one coming in as the function parameter
if (item) {
newItem.checked = !newItem.checked;
setCheckboxesToCheckedHandler(newItem.checked);
}
return newItem;
});
I'm trying to customizing this dropdown component.
https://codesandbox.io/s
state = {
activeOptionIndex: -1,
isOpen: false,
};
getAdditionalProps = (index, props) => ({
onSelect: this.onSelect,
index,
selected: index === this.state.activeOptionIndex,
...props,
});
getChildrenOptionssWithProps = () => {
return Children.map(this.props.children, (child, index) =>
cloneElement(child, this.getAdditionalProps(index, child.props)),
);
};
getActiveOptionLabel = () => {
const { children } = this.props;
const { activeOptionIndex } = this.state;
const currentChildren = children[activeOptionIndex];
if (currentChildren) {
return currentChildren.props.children;
}
return false;
};
toggleList = () => {
this.setState({ isOpen: !this.state.isOpen });
};
onSelect = (optionIndex, value) => {
const { onSelect } = this.props;
this.setState({
activeOptionIndex: optionIndex,
isOpen: false,
});
if (onSelect !== 'undefined') onSelect(value);
};
render() {
const childrenOptionssWithProps = this.getChildrenOptionssWithProps();
const label = this.getActiveOptionLabel();
return (
<div className="Dropdown">
<Button onClick={this.toggleList} text={label || 'Выберите...'} />
{this.state.isOpen && (
<div className="Dropdown__list">{childrenOptionssWithProps}</div>
)}
</div>
);
}
}
I don't wanna show the selected list on all the lists.
Let's say there are list A, list B, list C. and when list B is selected, I dont want this list B to be shown on all the lists. so only list A and list B will be showing on the lists.
getAdditionalProps()
getChildrenOptionssWithProps()
I think these two functions are the points to solve this problem but have no idea how to manage it... Before the mapping from getChildrenOptionssWithProps(), I can add filter function i guess?
Can anyone please help me??
I modified your getChildrenOptionssWithProps function as follows and it seems to be doing what you are looking for.
getChildrenOptionssWithProps = () => {
return Children.map(this.props.children, (child, index) => {
if (index !== this.state.activeOptionIndex){
return cloneElement(child, this.getAdditionalProps(index, child.props));
}
return;
}
);
};