React handling multiple checkboxes - reactjs

I made a PoC to see how to handle my change detection on a dynamic list of checkboxes (note, i do not know beforehand how many checkboxes i have.) I created a n ES6 map (Dictionary) that tracks the checked state of each individual checkbox. but for some reason I get the following error:
A component is changing an uncontrolled input of type checkbox to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component
Usually when my number of form input fields is known i track them via the state, but how would one handle this case. The logic works fine, but I need to get rid of the error.
My app code:
import React, { Component } from "react";
import Checkbox from "./checkbox";
class App extends Component {
constructor(props) {
super(props);
this.state = {
checkedItems: new Map()
};
this.handleChange = this.handleChange.bind(this);
}
handleChange = e => {
const item = e.target.name;
const isChecked = e.target.checked;
this.setState(prevState => ({
checkedItems: prevState.checkedItems.set(item, isChecked)
}));
};
deleteCheckboxState = (name, checked) => {
const updateChecked = typeof checked === "undefined" ? true : false;
this.setState(prevState => prevState.checkedItems.set(name, updateChecked));
};
clearAllCheckboxes = () => {
const clearCheckedItems = new Map();
this.setState({ checkedItems: clearCheckedItems });
};
render() {
const checkboxes = [
{
name: "check-box-1",
key: "checkBox1",
label: "Check Box 1"
},
{
name: "check-box-2",
key: "checkBox2",
label: "Check Box 2"
},
{
name: "check-box-3",
key: "checkBox3",
label: "Check Box 3"
},
{
name: "check-box-4",
key: "checkBox4",
label: "Check Box 4"
}
];
const checkboxesToRender = checkboxes.map(item => {
return (
<label key={item.key}>
{item.name}
<Checkbox
name={item.name}
checked={this.state.checkedItems.get(item.name)}
onChange={this.handleChange}
type="checkbox"
/>
</label>
);
});
const checkboxesDeleteHandlers = checkboxes.map(item => {
return (
<span
key={item.name}
onClick={() =>
this.deleteCheckboxState(
item.name,
this.state.checkedItems.get(item.name)
)
}
>
{item.name}
</span>
);
});
return (
<div className="App">
{checkboxesToRender}
<br /> {checkboxesDeleteHandlers}
<p onClick={this.clearAllCheckboxes}>clear all</p>
</div>
);
}
}
export default App;
The checkbox reusable component:
import React from "react";
class Checkbox extends React.Component {
render() {
return (
<input
type={this.props.type}
name={this.props.name}
checked={this.props.checked}
onChange={this.props.onChange}
/>
);
}
}
export default Checkbox;

The problem is that the checked state is initially undefined, which is a falsy value, but interpreted as not provided.
So you can simply ensure that the falsy state will actually be false by using !!.
So change the line
checked={this.state.checkedItems.get(item.name)}
to this
checked={!!this.state.checkedItems.get(item.name)}

React gives you this warning because it likes that you chose between controlled and uncontrolled components.
In the case of a checkbox input the component is considered controlled when its checked prop is not undefined.
I've just given a default value for checked and changed the code testing for undefined a little bit.
The warning should be gone.
// import React, { Component } from "react";
class Checkbox extends React.Component {
static defaultProps = {
checked: false
}
render() {
return (
<input
type={this.props.type}
name={this.props.name}
checked={this.props.checked}
onChange={this.props.onChange}
/>
);
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
checkedItems: new Map()
};
this.handleChange = this.handleChange.bind(this);
}
handleChange = e => {
const item = e.target.name;
const isChecked = e.target.checked;
this.setState(prevState => ({
checkedItems: prevState.checkedItems.set(item, isChecked)
}));
};
deleteCheckboxState = (name, checked) => {
const updateChecked = checked == null ? true : false;
this.setState(prevState => prevState.checkedItems.set(name, updateChecked));
};
clearAllCheckboxes = () => {
const clearCheckedItems = new Map();
this.setState({ checkedItems: clearCheckedItems });
};
render() {
const checkboxes = [
{
name: "check-box-1",
key: "checkBox1",
label: "Check Box 1"
},
{
name: "check-box-2",
key: "checkBox2",
label: "Check Box 2"
},
{
name: "check-box-3",
key: "checkBox3",
label: "Check Box 3"
},
{
name: "check-box-4",
key: "checkBox4",
label: "Check Box 4"
}
];
const checkboxesToRender = checkboxes.map(item => {
return (
<label key={item.key}>
{item.name}
<Checkbox
name={item.name}
checked={this.state.checkedItems.get(item.name) || false}
onChange={this.handleChange}
type="checkbox"
/>
</label>
);
});
const checkboxesDeleteHandlers = checkboxes.map(item => {
return (
<span
key={item.name}
onClick={() =>
this.deleteCheckboxState(
item.name,
this.state.checkedItems.get(item.name)
)
}
>
{item.name}
</span>
);
});
return (
<div className="App">
{checkboxesToRender}
<br /> {checkboxesDeleteHandlers}
<p onClick={this.clearAllCheckboxes}>clear all</p>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Related

Core-UI React CMultiSelect reset not working

I am trying to reset Core UI ReactJS CMultiSelect component with Reset button. I have used setState method to reset the value. As soon as I click on the reset button, the state value changes, but immediately onChange method of CMultiSelect is called and the existing value is retained.
Below is the code snippet I'm trying.
import React from 'react'
import { CRow, CMultiSelect, CFormInput, CButton } from '#coreui/react-pro'
class TestForm extends React.Component<{}, { textVal: string; dropdownVal: string[] }> {
constructor(props: any) {
super(props)
this.state = { textVal: '123', dropdownVal: [] }
}
setTextVal(newVal: string) {
this.setState({ textVal: newVal })
}
setTest(newVal: string[]) {
this.setState({ dropdownVal: newVal })
}
render() {
return (
<div className="row m-5">
<div className="col-sm-6">
<CFormInput
type="text"
value={this.state.textVal}
onChange={(evt) => {
this.setTextVal(evt.target.value)
}}
></CFormInput>
</div>
<div className="col-sm-6">
<CMultiSelect
multiple={false}
options={[
{
value: '1',
text: '1',
selected: this.state.dropdownVal.indexOf('1') >= 0,
},
{
value: '2',
text: '2',
selected: this.state.dropdownVal.indexOf('2') >= 0,
},
{
value: '3',
text: '3',
selected: this.state.dropdownVal.indexOf('3') >= 0,
},
]}
onChange={(val) => {
console.log('on change called', val)
this.setTest(
val.map((x) => {
return x.value.toString()
}),
)
}}
></CMultiSelect>
</div>
<div className="col-sm-6">
<CButton
className="mt-3"
type="reset"
value="Reset"
onClick={() => {
this.setTest([])
this.setTextVal('')
}}
>
Reset
</CButton>
</div>
</div>
)
}
}
export default TestForm
When I hit the reset button, value of the text field resets, but not the multi-select dropdown.
try calling the reset method on the component instance. You can do this by saving a reference to the CMultiSelect instance in your component's state, and then calling the reset method on that instance in your reset handler.
class MyComponent extends React.Component {
state = {
select: null,
};
handleReset = () => {
this.state.select.reset();
}
render() {
return (
<div>
<CMultiSelect
ref={(select) => this.setState({ select })}
onChange={this.handleChange}
/>
<button onClick={this.handleReset}>Reset</button>
</div>
);
}
}

React - On change of radiobutton component, update textbox component state

I have 3 components. Main Component named 'XYZ' which contains 2 other components named 'RadioButton' and 'Textbox'. I need to update state of Textbox component (Setting value to 500) on change of RadioButton component. Below is my code. I am new to React development. Any help would be surely appreciated!
Main Component - XYZ.tsx
const XYZ = (props) => {
let lossCapacityValues = [
{ value: "Average", text: "Average" },
{ value: "Medium", text: "Medium" }
];
const lossCapacityChange = (event) => {
let ele = $(event.target);
};
return (
<div className="mel-box">
<RadioButton groupName="radio-loss-type" selectedValue="2" items={lossCapacityValues} onChange={lossCapacityChange} />
<TextBox value="1000" />
</div>
)
};
export default XYZ;
Child Component - 1- RadioButton.tsx
const RadioButton = (props) => {
const onChange = (event) => {
props.onChange && props.onChange(event);
}
return (
<div>
{
props.items.map((i: { "text": string, "value": string }, index: number) => {
return (
<div key={index} className="radiobutton-option-div">
<input
className="radio-custom"
type="radio"
name={props.groupName}
value={i.value}
defaultChecked={i.value === props.selectedValue}
onChange={onChange}
/>
<label className="radio-custom-label">
<span className="radio-custom-text">{i.text}</span>
</label>
</div>
)
})
}
</div>
);
};
export default RadioButton;
Child Component - 2- TextBox.tsx
const TextBox = (props) => {
const [state, setState] = React.useState({
value: props.value
});
const handleChange = (evt) => {
setState({ value: evt.target.value });
};
return (
<React.Fragment>
<div className="input-text">
<input
type="text"
value={state.value}
onChange={handleChange}
/>
</div>
</React.Fragment>
)
};
export default TextBox;
This can be done by lifting the state up, out of the TextBox component into the XZY component.
Working demo
In XYZ, put the text state handlers:
const XYZ = (props) => {
const [textState, setTextState] = React.useState({
value: 1000
});
const handleTextChange = (evt) => {
setTextState({ value: evt.target.value });
};
....
....
};
Pass them as props:
<TextBox value={textState.value} onChange={handleTextChange} />
Change your input to use them:
<input
type="text"
value={props.value}
onChange={props.onChange}
/>
In your XYZ you can check the selected radio value and set the text state accordingly.
const lossCapacityChange = (event) => {
//let ele = $(event.target);
if(event.target.value == 'Average'){
setTextState({ value: 500 });
}
};
Side note: you should avoid the use of jQuery in React unless there is a good reason for it. React uses a Virtual DOM which can be affected by jQuery use.

Radio buttons for redux form field

It doesn't work correctly, I want to select only one value, but I can select one and more... and can't deselect
there is code for that function.
const RadioButtonField = ({type, options, disabled, onChange}) => {
const handleChange = (value) => {
onChange(type, value);
};
return (
<div>
{options.map(({ value, label }) =>
<Radio.Group key={value}>
<Radio
disabled={disabled}
onChange={() => handleChange(value)}
>
{label}
</Radio>
</Radio.Group>
)}
</div>
);
};
You can try this for single selection and also you can reselect too
import React from "react";
import "./styles.css";
import Radio from "#material-ui/core/Radio";
export default class App extends React.Component {
state = {
selectedValue: null,
radioOptions: [
{ id: 1, title: "1" },
{ id: 2, title: "2" },
{ id: 3, title: "3" },
{ id: 4, title: "4" }
]
};
handleChange = id => {
this.setState({
selectedValue: id
});
};
render() {
const { selectedValue, radioOptions } = this.state;
return (
<div className="App">
{radioOptions.map(option => {
return (
<div className="radio-parent">
<lable
onClick={() => this.handleChange(option.id)}
className="radio-btn"
>
<Radio
color="default"
value={option.id}
name="radioValue"
checked={selectedValue == option.id}
/>
{option.title}
</lable>
</div>
);
})}
</div>
);
}
}
codesandBox link for Demo
You can refer to the following code:
class App extends React.Component {
state = {
initialValue: "A",
options: ["A", "B", "C", "D"]
};
onChange = e => {
console.log("radio checked", e.target.value);
this.setState({
value: e.target.value
});
};
render() {
return (
<Radio.Group value={this.state.initialValue}>
{this.state.options.map((value, index) => {
return (
<Radio onChange={this.onChange} key={index} value={value}>
{" "}
{value}{" "}
</Radio>
);
})}
</Radio.Group>
);
}
}
Working codesandbox demo
I think you do not need to pass anything to the function. Whenever the option will be clicked, the event (e) object will be passed to onChange(e) and you will get the value of clicked item and checked value in onChange(e) e.target.value and e.target.checked respectively.

Passing a function as a prop in react, Cannot read property 'edit' of undefined

I'm learning ReactJS and I'm creating a editable Pokémon list based on this guide.
When I try to pass a function to edit a list item (at this point I want to click the item and get the name), I get TypeError: Cannot read property 'edit' of undefined on the following line of the addPokemon function: onClick={() => this.edit(pokemon.name)}
Code:
PkmnForm
import React, { Component } from "react";
import PkmnList from "./PkmnList";
class PkmnForm extends Component {
static types = [
'Bug',
'Dragon',
'Ice',
'Fighting',
'Fire',
'Flying',
'Grass',
'Ghost',
'Ground',
'Electric',
'Normal',
'Poison',
'Psychic',
'Rock',
'Water'
]
constructor(props) {
super(props);
this.state = {
name: '',
type: '',
pokemons: [],
caught: false,
};
}
handleSubmit = (event) => {
var pokemon = {
name: this.state.name,
type: this.state.type,
caught: this.state.caught
};
this.setState({
name: '',
type: '',
caught: false,
pokemons: this.state.pokemons.concat(pokemon)
});
event.preventDefault()
}
handleChange = (event) => {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
}
handleTypeChange = (event) => {
this.setState({
type: event.target.value,
})
}
editPokemon(name) {
console.log(name);
}
render() {
return (
<div>
<div>
<h1>Register a Pokémon</h1>
<form onSubmit={this.handleSubmit}>
<input
required
placeholder=" Name"
name="name"
onChange={this.handleChange}
value={this.state.name}
/>
<br />
<select
name="type"
required
value={this.state.type}
onChange={this.handleChange}
>
<option value='' disabled>Type</option>
{PkmnForm.types.map(
optionValue => (
<option
key={optionValue}
value={optionValue}
>
{optionValue}
</option>
)
)}
</select>
<br />
<label>
Caught
<input
name="caught"
type="checkbox"
checked={this.state.caught}
onChange={this.handleChange}
/>
</label>
<br />
<button type="submit">Add Pokemon</button>
</form>
</div>
<div>
<PkmnList
pokemons={this.state.pokemons}
edit={this.editPokemon}
/>
</div>
</div>
);
}
}
export default PkmnForm;
PkmnList
import React, { Component } from 'react';
class PkmnList extends Component {
constructor(props) {
super(props);
this.edit = this.edit.bind(this);
}
edit(name) {
this.props.edit(name);
}
addPokemon(pokemon) {
return <li
onClick={() => this.edit(pokemon.name)}
key={pokemon.name}
>
{pokemon.name} – {pokemon.type} {pokemon.caught ? '(caught)' : ''}
</li>
}
render() {
var pokemons = this.props.pokemons;
var listItems = pokemons.map(this.addPokemon);
return (
<ul>
{listItems}
</ul>
);
}
}
export default PkmnList;
Thanks :-)
The problem is in this line:
var listItems = pokemons.map(this.addPokemon);
Here, you are passing this.addPokemon as the function to map. But that function is not bound to this, so inside of it, this is not available.
You either have to bind it explicitly by calling .bind(this), just like you did with the edit function:
var listItems = pokemons.map(this.addPokemon.bind(this));
Or you can pass an arrow function that calls the method:
var listItems = pokemons.map(x => this.addPokemon(x));
You need to bind editPokemon like so:
constructor(props) {
super(props);
this.editPokemon = this.editPokemon.bind(this);
}
Or you can also use arrow functions, which have proper scoping:
editPokemon = (pokemon) => {
...
}

React Checkbox Group - Set initial state from API

I have a CheckboxGroup component which takes in an options array prop and generates CheckboxInput components. On page load I make a call to an API which returns an array of pre-selected checkboxes (delivered to the value prop). Depending on the logged in user, this call can return an empty array or a selection of previously selected checkbox options.
The following code successfully takes the response of the API call and sets the relevant checkboxes to 'checked'. The issue I have is that this code doesn't allow me to make changes to the checkboxes after page load (clicking a checkboxes has no effect).
I think there is also some disconnect between the initial selectedCheckboxes state and the value of the API call but I read that setting props as initial state is an anti-pattern (e.g. selectedCheckboxes: props.value,).
export default class CheckboxGroup extends Component {
constructor(props) {
super(props);
this.state = {
selectedCheckboxes: [],
};
}
addCheckboxToSelected = (id) => {
if (this.state.selectedCheckboxes.includes(id)) {
// Remove checkbox from array and update state
const filteredArray = this.state.selectedCheckboxes.filter(item => item !== id);
this.setState({ selectedCheckboxes: filteredArray });
} else {
// Add checkbox to array and update state
this.setState({ selectedCheckboxes: this.state.selectedCheckboxes.concat(id) });
}
}
checkIfSelected = (checkboxValue) => {
const preSelectedCheckboxes = this.props.value;
let selectedBool = false;
preSelectedCheckboxes.some(function(object) {
if (object.id === checkboxValue) {
selectedBool = true;
}
return false;
});
return selectedBool;
}
render() {
const { label, name, options } = this.props;
return (
<div className="form-group form-inline">
<span className="checkboxgroup-heading">{label}</span>
<div className="form-group-container">
{options.map(object => (
<CheckboxInput
key={object.value}
name={name}
label={object.label}
onChange={this.addCheckboxToSelected}
value={object.value}
checked={this.checkIfSelected(object.value)}
/>
))}
</div>
</div>
);
}
}
This is the stateless CheckboxInput component
const CheckboxInput = ({ name, label, onChange, value, checked }) => {
return (
<div className="field form-group filter-input">
<input
type="checkbox"
id={value}
name={name}
value={value}
onChange={() => onChange(value)}
checked={checked}
/>
<label htmlFor={value} className="form-control">{label}</label>
</div>
);
};
Check the following code snippet. This might help. Let me know if you have questions.
const CheckboxField = ({checked, onChange}) => {
return (
<input type="checkbox" checked={checked} onChange={ev => onChange(ev.target.checked)} />
);
};
class App extends React.Component {
constructor() {
super();
this.state = {
options: [{id: "1", checked: true}, {id: "2", checked: false}]
};
}
handleCheckboxChange(checked, option) {
const {options} = this.state;
var cOptions = [...options];
for(var i in cOptions) {
if(cOptions[i].id == option.id) {
cOptions[i].checked = checked;
}
}
this.setState({
options: cOptions
}, () => console.log(options));
}
render() {
const {options} = this.state;
return (
<div>
{
options.map(option => {
return (
<CheckboxField key={option.id} checked={option.checked} onChange={value => this.handleCheckboxChange(value, option)} />
)
})
}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>

Resources