How to control checkBox's checked props? - reactjs

I made checkbox component and inside,
set isChecked state to remember checked values even after re-render in case of
new data will be fetched.
but because of this, isChecked state makes every checkbox being checked.
here is example. https://codesandbox.io/s/withered-sun-pm8o9?file=/src/App.js
how can I control checkboxes individually?

Issue
You've only a single isChecked value to toggle all the checkboxes.
A Solution - There are multiple
Store an array of checked booleans. Initialize the state to an array computed from the items prop.
const [isChecked, setIsChecked] = useState(items.map((_) => false));
Update check onChange handler to also consume an index value. Note here that I've also rewritten this handler as a curried function that consumes the item and index and returns the callback function that takes the onChange event object. Use the index to map the previous state to the next state, saving the checked value when the index matches.
const check = (item, index) => (e) => {
const { checked } = e.target;
handleCheck(checked, item);
setIsChecked((isChecked) =>
isChecked.map((el, i) => (i === index ? checked : el))
);
};
When mapping the items prop to the checkbox inputs, use the index to pass to the onChange handler and to access the isChecked checked value.
return items.map((item, i) => (
<FormControlLabel
value="start"
control={
<Checkbox
...
onChange={check(item, i)} // <-- pass item and index i
checked={isChecked[i]} // <-- get checked value from state by index i
/>
}
label={item}
labelPlacement="end"
/>
));
Demo

Related

Unchecking a checkbox in react from several checkbox groups (I'm using React hooks)

I have several checkboxes running in several checkbox groups. I can't figure out how to uncheck (thus changing the state) on a particular checkbox. FOr some reason I can't reach e.target.checked.
<Checkbox
size="small"
name={item}
value={item}
checked={checkboxvalues.includes(item)}
onChange={(e) => handleOnChange(e)}
/>
and my function
const handleOnChange = (e) => {
const check = e.target.checked;
setChecked(!check);
};
I made a working sample of the component in this sandbox.
You need to create handleOnChange function specific to each group. I have created one for Genre checkbox group in similar way you can create for other groups.
Here is handler function.
const handleOnChangeGenre = (e) => {
let newValArr = [];
if (e.target.checked) {
newValArr = [...state.pillarGenre.split(","), e.target.value];
} else {
newValArr = state.pillarGenre
.split(",")
.filter((theGenre) => theGenre.trim() !== e.target.value);
}
setState({ ...state, pillarGenre: newValArr.join(",") });
};
pass this function as handleOnChange prop to CustomCheckboxGroup as below.
<CustomCheckboxGroup
checkboxdata={genres}
checkboxvalues={state.pillarGenre}
value={state.pillarGenre}
sectionlabel="Genre"
onToggleChange={handleGenreSwitch}
togglechecked={genreswitch}
handleOnChange={handleOnChangeGenre}
/>
comment your handleOnChange function for testing.
check complete working solution here in sandbox -
complete code
Here's how I'd do it: https://codesandbox.io/s/elastic-pateu-flwqvp?file=/components/Selectors.js
I've abstracted the selection logic into a useSelection() custom hook, which means current selection is to be found in store[key].selected, where key can be any of selectors's keys.
items, selected, setSelected and sectionLabel from each useSelection() call are stored into store[key] and spread onto a <CustomCheckboxGroup /> component.
The relevant bit is the handleCheck function inside that component, which sets the new selection based on the previous selection's value: if the current item is contained in the previous selected value, it gets removed. Otherwise, it gets added.
A more verbose explanation (the why)
Looking closer at your code, it appears you're confused about how the checkbox components function in React.
The checked property of the input is controlled by a state boolean. Generic example:
const Checkbox = ({ label }) => {
const [checked, setChecked] = useState(false)
return (
<label>
<input
type="checkbox"
checked={checked}
onChange={() => setChecked(!checked)}
/>
<span>{label}</span>
</label>
)
}
On every render, the checked value of the <input /> is set according to current value of checked state. When the input's checked changes (on user interaction) the state doesn't update automatically. But the onChange event is triggered and we use it to update the state to the negative value of the state's previous value.
When dealing with a <CheckboxList /> component, we can't serve a single boolean to control all checkboxes, we need one boolean for each of the checkboxes being rendered. So we create a selected array and set the checked value of each <input /> to the value of selected.includes(item) (which returns a boolean).
For this to work, we need to update the value of selected array in every onChange event. We check if the item is contained in the previous version of selected. If it's there, we filter it out. If not, we add it:
const CheckboxList = ({ items }) => {
const [selected, setSelected] = useState([])
const onChecked = (item) =>
setSelected((prev) =>
prev.includes(item)
? prev.filter((val) => val !== item)
: [...prev, item]
)
return items.map((item) => (
<label key={item}>
<input
type="checkbox"
checked={selected.includes(item)}
onChange={() => onChecked(item)}
/>
<span>{item}</span>
</label>
))
}
Hope that clears things up a bit.
The best way to do it, it's to save selected checkboxes into a state array, so to check or uncheck it you just filter this state array based on checkbox value property that need to be unique.
Try to use array.some() on checkbox property checked. To remove it it's just filter the checkboxes setted up in the state array that are different from that single checkbox value.

React Hooks SetState Method isn't updating the state at all

I am using a Material UI Select Component to render a simple drop down menu, with its value as a state declares using the useState method.
const [collaboratingTeams, setCollaboratingTeams] = useState([])
The below code is of the Select Component, with its value and the corresponsing handler function in its onChange prop.
<Select
validators={["required"]}
errorMessages={["this field is required"]}
select
multiple
variant="outlined"
value={collaboratingTeams}
name="collaboratingTeams"
onChange={(e) => handleSelectCollaboratingTeams(e)}
helperText="Select Collaborating Teams "
>
{arrTeams.map((option, index) => (
<MenuItem
key={option.teamId}
value={option.teamId}
variant="outlined"
>
<Checkbox
checked={collaboratingTeams.indexOf(option.teamId) !== -1}
/>
<ListItemText primary={option.teamValue} />
</MenuItem>
))}
</Select>
The below code is the function that triggers when a drop down data is changed.
This function sets the state, which should then technically update the Select's selected options.
const handleSelectCollaboratingTeams =(e)=>{
setCollaboratingTeams(e.target.value)
}
The issue is, the setCollaboratingTeams method isn't updating the state only. I understand that the setstate method in hooks works so coz of its asynchronous nature but at some point it should display up right. Don't understand where I'm going wrong.
I expect the collaboratingTeams array to be updated with a new value when a new value is selected by the user.
you should define the new state for storing the selected item.
Example for class component:
state = {
selectedOption: null,
};
handleChange = selectedOption => {
this.setState({ selectedOption });
};
Example for functional component(using React-hook):
const [selectedOption, setSelectedOption] = useState(null);
handleChange = selectedOption => {
setSelectedOption(selectedOption);
};
dont use arrow function with onchange it often used when we need to pass id or some other data

Customize Array State in ReactJs with ES6

i am fetching some data (in array) from api and assigning it to state called items. then calling an Item component for each item of the state by following code:
<div>
{items.length} results found for {search_term}
{items.map((item) =>
(item.active_price > 0 && item.show) ? <Item key={item.id} details={item} items={items} setItems={setItems}/> : ""
)}
</div>
The array looks like this:
Next plan is adding a remove button in Item component, which will change value of show to false (for the item clicked) in the items state. What I am thinking is, as the state will change, the items state will be re-rendered and the item turned to false will be hidden from view. I am adding a onCLick event listener on the button and the function is:
const handleClick = () => {
console.log(({...props.items,[props.details.number - 1]:false}))
// props.setItems(prevState=>({...prevState,[props.details.number - 1]:false}))
}
I'll call props.setItems later but for now, I am trying to see if I can just edit that element to turn the show into false. the code above replace the whole index with false.
in the onClick function, I only want to edit the value of show, not replace the entire entry. I have tried:
({...props.items,[props.details.number - 1][show]:false})
and
({...props.items,[props.details.number - 1].show:false})
It shows syntax error before [show] and .show respectively. How can I edit my handleClick function to edit the value of show properly.
Thanks.
It looks like you are converting your array into an object in your current method. (the first screen shot is an array, the second is an object). You're going to run into issues there.
To just remove the item from the array, it would be easiest just to use .filter();
const handleClick = () => {
props.setItems(
prevState => prevState.filter(item => item.number !== props.details.number)
);
}
To set the show property is a bit more complicated. This is pretty standard way to do so without mutating.
const handleClick = () => {
props.setItems(prevState => {
const itemIndex = prevState.findIndex(
item => item.number == props.details.number
);
// Using 'props.details.number - 1' will not be a valid index when removing items
return [
...prevState.slice(0, itemIndex), // Your state before the item being edited
{...prevState[itemIndex], show: false}, // The edited item
...prevState.slice(itemIndex + 1) // The rest of the state after the item
];
});
}
You can try the following syntax of code, assuming items is state here-
const [items,setItems]=.....
The following is the code
const handleClick = () => {
setItems({
...items,
show: false,
})}
It will update the items state with value show as false.

How do I update an array using useState?

I have the following scenario:
DisplayList component, which renders a list. The list record is passed in via props (this contains the list name, description, author etc), along with the initial items to be listed, and the current 'mode' (can be 'r', 'g' or 'a'). The component displays the list information, and then renders a DisplayItem component for each list item. The DisplayItem component also takes a callback function, so that when the user clicks on an item in the list, it becomes 'tagged' with the current mode (ie the 'mode' property of the item changes).
const DisplayList = (props) => {
// Get list object (contains list name etc)
// and current mode (r, a or g)
const { list, currentMode } = props
// Array of items to show
// This is an array of objects, with each object representing a single item
// eg {name: itemname, mode: r}
const [ items, setItems] = useState(props.items)
// Callback function - triggered when the rendered DisplayItem is clicked
// This changes the 'mode' of the item to the current mode
function handleItemClick(index) {
items[index].mode = currentMode
setItems(items) // At this point the list of items should re-render
}
}
return (
<div className="displayItem">
{
items.map(item => <DisplayItem key={uuid()} item={item} handleCellClick={handleItemClick} />)
}
</div>
)
}
The problem I'm having is that clicking an item seems to trigger the handleItemClick function to update the itemList, but when it gets to setItems, it doesn't re-render.
You are not passing the index to your callback.
try this:
items.map({item, index} => <DisplayItem key={uuid()} item={item} handleCellClick={() => handleItemClick(index)} />)
edited as pointed by Brian Thompson at the comments

How to handle a different type of event with a handler, specifically a dropdown and not a textfield. SPFX/React

This is a continuation on from:
How to create a handler function which 'handles' multiple textfields but also character counter textfields as well
I now want to use this handler to 'handle' a drop down along with textfields. I have this so far:
public handleChange = (evt: any) => {
const {value} = (evt.target as any);
const obj = {
[evt.target.name]: value,
};
this.setState(prevState => ({prevState, ...obj}));
}
Which works for textfields.
I now want it to handle a Dropdown component from office-ui-fabric. I've tried creating a completely different handler like this:
public onDropDownChange = (evt: any) => {
const {value} = (evt.target as any);
this.setState({ value: evt.target.value});
}
The above, experimental, onDropDownChange does not work. It gives an error saying value does not exist on event. I'm fairly sure this is because whatever is returned from a drop down is not 'value' and could quite well be 'item'.
I could write a function specifically for this drop down but I'm trying to cut down the amount of code written using handlers.
JSX:
<Dropdown
selectedKey={this.state.SelectedJobPlanDD}
placeholder="Select..."
onChange={this.onDropDownChange}
options={jobPlanDDOptions}
defaultSelectedKey={this.state.JobPlanDD}
/>
State:
SelectedJobPlanDD: undefined,
JobPlanDD: null,
BTW: If I log the change of the drop down I get undefined, so that's the start of the problem.
Also, here is a 'standalone' function that I've used in the past for drop downs, it may help:
private _dropDownFunction = (item: IDropdownOption) => {
this.setState({ SelectedItem: (item && item.key) ? item.key.toString() : null })
Any idea what I could use? My understanding of event handlers and ultimately basic JS logic is limited, but I learn a great deal from any advice given on SO.
UPDATE: From LevelGlow's answer I've been able to return the selected item from the drop down. I'm using this for the handler:
public onDropDownChange = (evt: any, item) => {
// const {item} = (evt.target as any);
console.log(evt+'evt with text');
console.log(evt); // THIS IS THE ONE THAT SHOWS THE SELECTED ITEM!
console.log(item.text+'item.text with text'); //Shows undefined
console.log(item); //shows 3 (I guess this is id of the item)
console.log(item.key+'item.key with text'); //shows undefined.
this.setState({
});
}
Now I'm not sure how I'd implement this into the handler to update the state item.
To obtain the selected item from the Dropdown, structure the Dropdown's onChange handler as the docs say, example below:
public onDropDownChange = (evt, item) => {
console.log(item.key);
console.log(item.text);
};
The onChange event here has a DOM object as target which stores only the text of the dropdown and it can be accessed by
evt.target.querySelector('span').innerText;
You can add the id prop to your Dropdown:
<Dropdown
id="JobPlanDD"
...
/>
Now the component generates a new id for you in the previously mentioned span tag and you can access it by calling
evt.target.querySelector('span').id; which returns "JobPlanDD-option"
And we will be using it as key for the state.
So your selectedKey should be:
selectedKey={(this.state["JobPlanDD-option"]) ? (this.state["JobPlanDD-option"]).key : undefined}
What this line doing is: do we have JobPlanDD-option in out state? if yes, we'll pass the JobPlanDD-option item's key to selectedKey, if we don't, we'll pass undefined.
Structure your shared handler like this:
public handleChange = (evt: any, item): void => {
if(evt.target.value){
//handle textfields here
} else {
//handle dropdowns here
const id = evt.target.querySelector('span').id; //get the span's id
//update state with the generated id and the selected option
this.setState({ [id] : item });
}
}
Since we updated the shared handler, we should update also how TextFields are calling it:
<TextField
onChange={(e) => this.handleChange(e, null)}
...
/>
Now both text fields and dropdowns should work with this shared handler.

Resources