Just a note, this question is NOT an asynchronous update problem (at least, I don't think it is).
I have a class component with the following content (heavily simplified to get right to the issue):
constructor(props) {
super(props);
this.state = {
aSelected: false;
bSelected: false
}
}
handleCheckboxChange = (e) => {
const { checked, value } = e.target;
console.log( 'checked: ', checked );
if(value=="a") {
this.setState( {aSelected: checked}, () => {
console.log('aSelected: ', this.state.aSelected);
console.log("---")
});
}
if(value=="b") {
this.setState( {bSelected: checked}, () => {
console.log('bSelected: ', this.state.bSelected);
console.log("---")
});
}
}
Somewhere inside the render return, I have this:
<input>
type="checkbox"
value="a"
onChange={this.handleCheckboxChange}
checked={this.state.aSelected}
disabled={ (this.state.aSelected || (!this.state.aSelected && !this.state.bSelected) ) ? false : true}
</input>
<input>
type="checkbox"
value="b"
onChange={this.handleCheckboxChange}
checked={this.state.bSelected}
disabled={ (this.state.bSelected || (!this.state.aSelected && !this.state.bSelected) ) ? false : true}
</input>
Here is the output logged in Chrome Developer Tools. As you can see, "checked" is toggled appropriately each time I selected and unselect the checkbox. However, the state of "selected" (should say "aSelected") is never changed and always has the initial state value of false. Anyone know why the value of "selected" (should say "aSelected") is never changed?
Edit: My goal is to create two checkbox items, where the user can only select ONE or select NONE. While one is selected, the other should be disabled.
When you call setState to update the state, React re-renders the component, which resets the checkbox back to it's default (i.e. unchecked) state.
You'll need to use the current state to manage the checkbox as well. The JSX should look something like:
<input
type="checkbox"
checked={this.state.aSelected}
onChange={this.handleCheckboxChange}
/>
In React's terms, this is what's known as a "controlled component" because React is fully responsible for keeping up with the input's state. Read more in the docs here:
https://reactjs.org/docs/forms.html#controlled-components
vs
https://reactjs.org/docs/uncontrolled-components.html
Edit to match the question's edits:
In your render function, be sure you're using this.state.aSlected. Note, you also still need the checked={this.state.aChecked} attribute as well, otherwise the checkbox will be unchecked on the next render.
Like:
<input
type="checkbox"
value="a"
onChange={this.handleCheckboxChange}
checked={this.state.aSelected}
// added for clarification *
disabled={this.state.aSelected || (!this.state.aSelected && !this.state.bSelected) ? false : true}
/>
Edit with Working Example
Here's a working CodeSandbox example where checking one checkbox disables the other:
class CheckboxComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
aSelected: false,
bSelected: false
};
}
handleCheckbox = (event) => {
if (event.target.name === "boxA") {
this.setState({ aSelected: event.target.checked });
} else if (event.target.name === "boxB") {
this.setState({ bSelected: event.target.checked });
}
};
render() {
let aDisabled = this.state.bSelected && !this.state.aSelected;
let bDisabled = this.state.aSelected && !this.state.bSelected;
return (
<div>
<label>
<input
type="checkbox"
name="boxA"
checked={this.state.aSelected}
onChange={this.handleCheckbox}
disabled={aDisabled}
/>
Checkbox A
</label>
<br />
<br />
<label>
<input
type="checkbox"
name="boxB"
checked={this.state.bSelected}
onChange={this.handleCheckbox}
disabled={bDisabled}
/>
Checkbox B
</label>
</div>
);
}
}
Related
I am trying to showing default value and want to disable it field . Values is showing successfully but did not update it state . Could someone please help me how to update state on disable field . I really tried hard but didn't find any solution .
Thanks
Code
componentWillReceiveProps(nextProps) {
const { projectData } = nextProps;
this.setState({
project: projectData,
});
}
componentDidMount() {
const { stan_rate_Per_sqft, category_charges, project } = this.state;
console.log("##### data", stan_rate_Per_sqft, category_charges);
if (
project &&
project.category_charges_apply &&
project.category_charges_apply === "yes"
) {
this.setState({
rate_Per_sqft: stan_rate_Per_sqft * parseInt(1 + category_charges),
});
}
}
In Render Method
<td>
<input
type="text"
className="inputStyle"
name="rate_Per_sqft"
value={rate_Per_sqft}
disabled={true}
placeholder="Category Charges"
/>
</td>
Your component is always disabled since it's hardcoded to true. If you want it to have a dynamic state you'll need to add key to your class state.
...
constructor(props) {
super(props);
this.state = { isDisabled: true }
}
...
Now, in your input field, you may use this state...
<input ... disabled={this.state.isDisabled} />
When isDisabled is set to false (using setState), the field will be enabled again and vice versa.
The disabled attribute on an <input> element works by passing in a boolean value. A truthy value will disable it, a falsy value will enable it.
With that in mind we could use the negated value of rate_Per_sqft to enable the input element. Perhaps something like this:
class App extends React.Component {
state = {
rate_Per_sqft: null
}
componentDidMount() {
// Simulate asynchronous operation
setTimeout(() => {
this.setState({ rate_Per_sqft: 550 })
}, 1500)
}
render() {
const { rate_Per_sqft } = this.state
return(
<div>
{!rate_Per_sqft && <span>Calculating charges..</span>}
<input
type="text"
name="rate_Per_sqft"
value={rate_Per_sqft}
disabled={!rate_Per_sqft}
placeholder="Category Charges"
/>
</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>
Note: I wasn't sure if you wanted the form disabled or enabled by default. So, I disabled it. Also, I simplified the code to keep the example short and simple.
This is driving me crazy. I have no problem with select drop downs and text fields, but for some reason, I cannot get checkboxes working in a controlled fashion. That it is I want them to 'toggle' and listen to this event in a parent component.
I understand there is a 'checked' property for inputs of type checkbox. Selecting a checkbox gives it a value of 'on'. I take this 'on' value and convert it to true or false, and update the component accordingly.
For some reason, I cannot get this to work, either it is always selected, or it is never selected (if I switch the boolean switch).
export class ControlledCheckbox extends React.Component {
constructor(props) {
super(props);
this.state = {
select: false,
};
}
render() {
console.info("this.state.select - " + this.state.select);
let sel = false;
if (this.state.select !== "on") {
sel = true;
} else {
sel = false;
}
return (
<div>
<input
type="checkbox"
checked={sel}
onChange={this.handleChangeCheckbox}
/>
</div>
);
}
handleChangeCheckbox = (e) => {
console.info("here e.currentTarget.value " + e.currentTarget.value);
this.setState({
select: e.currentTarget.value,
});
//call passed through callback here
};
}
value serves a different purpose for checkbox inputs and you should not use it to define your state's value. You need to take a look at the e.currentTarget.checked instead.
export class ControlledCheckbox extends React.Component {
constructor(props) {
super(props);
this.state = {
select: false,
}
}
handleChangeCheckbox = e => {
this.setState({
select: e.currentTarget.checked // <--
})
}
render() {
return (
<div>
<input type="checkbox"
checked={this.state.select}
onChange={this.handleChangeCheckbox} />
</div>
);
}
}
If you are working with multiple inputs (not only checkboxes) you can follow the approach suggested by react docs, where you can cover multiple input types with only one setState. Keep in mind to define a name in this case, so you can separate your inputs:
handleInputChange(event) {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
}
render() {
return (
<div>
<input type="checkbox"
name="hasValue"
checked={this.state.select}
onChange={this.handleChangeCheckbox} />
</div>
);
}
Your question is about controlled inputs, however your checkbox isn't controlled yet. You rely on the value stored inside checkbox, not inside the state.
this.setState({
select: e.currentTarget.value, // here
});
Use a value that comes from the state instead.
this.setState((prevState) => ({
select: !prevState.select,
}));
Note: You can remove the conditions from the render, they are redundant.
I have Candidate list as a parent and Candidate as a child.
On Candidate list there is a Select All input so i bind function to it to set state if candidates are being selected and passing that state to child.
That one part works but those inputs are not mutable by itself just by parent Select All can be changed.
This is what does it looks like:
CandidateList Component(Parent):
class CandidateList extends React.Component {
constructor(props) {
super(props);
this.state = {
candidateList: null,
candidateSelected: false
}
}
onSelectCandidatesClick() {
this.setState({
candidateSelected: !this.state.candidateSelected
});
}
render() {
return (
<div className="CandidateList">
<div className="__selection">
<input className="checkbox" type="checkbox" id="selectAll" onClick={() => this.onSelectCandidatesClick()}/>
<label htmlFor="selectAll"><span id="__label">Select All</span></label>
</div>
</div>
);
}
}
Candidate Component(Child):
class Candidate extends React.Component {
constructor(props) {
super(props);
this.state = {
candidateSelected: false
}
}
componentWillReceiveProps(nextProps) {
this.setState({
candidateSelected: nextProps.selectCandidate
});
}
render() {
return (
<div className="Candidate">
<div className="__select">
<input className="checkbox" type="checkbox" id="candidate" data-candidate-id={this.props.candidateData.id} checked={this.state.candidateSelected} />
<label htmlFor="candidate"></label>
</div>
</div>
);
}
}
Thanks for all suggestions and help.
You need to give the checkbox input in your child component a way to process its change based on an input event.
<input checked={this.state.candidateSelected} onChange={e => {}} />
At the moment you're passing props in to say if it's checked or not so it can be an empty function.
Personally I think this is the wrong approach in any case. It's not an input because the user isn't inputting anything directly to that element.
Maybe you should have a prop called checked and if it is checked then show a check icon rather than an input.
{ this.props.checked ? <div className="checked"></div> : <div className="unchecked"></div>}
or something along those lines.
You can set the checked value of the child based on the props.candidateSelected (if the select all was selected from the parent)
or based on the state (if the checkbox was selected from the child)
You have to add an onclick event handler that will change the state when the checkbox in the child is clicked
class Candidate extends React.Component {
constructor(props) {
super(props);
this.state = {
candidateSelected: false
}
}
onSelectCandidatesClick() {
this.setState({
candidateSelected: !this.state.candidateSelected
});
}
render() {
return (
<div className="Candidate">
<div className="__select">
<input className="checkbox" type="checkbox" id="candidate" data-candidate-id={this.props.candidateData.id}
onClick={() => this.onSelectCandidatesClick()}
checked={this.state.candidateSelected || this.props.candidateSelected} />
<label htmlFor="candidate"></label>
</div>
</div>
);
}
}
This worked for me, but probably not the best option:
checked={isMenuOpen}
onClick={() => toggleDropdown()}
onChange={e => {}}
Have you made custom label for all inputs? 😃 this has gotten me before
I'm currenty having some issues with radio buttons in React.js. For some reason the button that was just unselected triggers the onChange event, while the button that was selected does nothing. I'm currently only able to get the value of the previously selected radio button.
I'm handeling the change event inside my App.js and passing it down to RadioButtons.jsx as a prop
Inside App.js:
constructor(){
super();
this.state = {
selectedOption: "medium_term"
}
}
handleOptionChange = changeEvent => {
this.setState({
selectedOption: changeEvent.target.value
});
this.getTop10();
console.log(this.state.selectedOption); //shows previous button
};
render() {
return (
<div className="App">
<RadioButtons onChange={this.handleOptionChange} selectedOption={this.state.selectedOption}/>
</div>
);
}
}
Example: one of my radio buttons
<div className="radio row mx-1 my-0">
<label>
<input
type="radio"
name="timeframe"
value="medium_term"
onChange={this.props.onChange}
checked={this.props.selectedOption === "medium_term"}
className="form-check-input"
/>
the last 6 months
</label>
</div>
setState is asynchronous so if you try to console log the state, it will return the previous state instead.
If you want to console.log the newly set state instantly after setting the state, you need to do it in the setState() callback like this:
this.setState({
selectedOption: changeEvent.target.value
}, function() {console.log(this.state.selectedOption); });
this.getTop10();
react gives me a warning: "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)."
However my checbox is change via the state property. Am I missing something obvious?
import React from 'react';
// Components
import Checkbox from './checkbox';
import HelpBubble from './helpBubble';
export default class CheckboxField extends React.Component {
constructor(props) {
super(props);
this.state = {value: props.value};
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
componentWillReceiveProps(nextProps) {
if (nextProps.value !== this.props.value) {
this.setState({value: nextProps.value});
}
}
render() {
const {label, meta = {}, help, disabled, required, onChange} = this.props;
return (
<label className="checkbox-wrap form-field">
<Checkbox
disabled={disabled}
type="checkbox"
onChange={(event) => {
onChange(event, !this.state.value);
}}
checked={this.state.value}/>
{label && (
<div className="checkbox-label">
{label}
{required && <div className="form-field__required"/>}
</div>
)}
{help && <HelpBubble help={help}/>}
{meta.error && meta.touched && (
<div className="input-error">{meta.error}</div>
)}
</label>
);
}}
Parent component:
handleChangeParams(key, value)
}
/>
Handle change params changes the value in model and calls server. Depending on server result, the value can change.
Thanks in advance.
If your state is initialized with props.value being null React will consider your Checkbox component to be uncontrolled.
Try setting your initial state so that value is never null.
this.state = { value: props.value || "" };
If you are using a checkbox react won't like a string either so instead try
this.state = { checkboxValue: props.checkboxValue || false };
Something worth noting about the above code snippet. When you set a state in the constructor from props, it is always best to set the state to a "controlled" value i.e. a tangible value such as an int, float, string, array, map, etc. The error you are getting is the result of props.value being set to null
So, Consider setting your constructor state like this:
this.state = {
value: props.value ? props.value : 'empty'
}
What is happening here is it is checking if props.value has a value, if it does it sets the state to props.value, if props.value is null, it sets the state to the string: `'empty'
Another simple way to do this would be to !! your props.checkboxValue value. That way even if it's undefined, !!props.checkboxValue will resolve to false.
this.state = { checkboxValue: !!props.checkboxValue };
In my case, I was using a prop from my redux store to set whether the checkbox was checked, simply defaulting the property to false worked for me.
e.g.
const MyComponent = ({
somePropFromRedux
}) => {
return <thatThridPartyCheckboxComponent checked={somePropFromRedux} />
}
becomes (only change is adding = false on Line 2)
const MyComponent = ({
somePropFromRedux = false
}) => {
return <thatThridPartyCheckboxComponent checked={somePropFromRedux} />
}
do not use e.target.checked in the inputbox onChange eventHandler method.
Correct way:
const [isChecked, setCheck] = useState(false);
const handler = (e) => {
setCheck(!isChecked);
};
<input
type="checkbox"
checked={isChecked}
onChange={handler}
/>