Component state not updating after unchecking checkbox in React - reactjs

I simply want to change the state of my app with a handleChange method for multiple checkboxes.
My state is changed from false to true when I click each of my checkboxes, but when I un-check it, my state doesn't change to reflect it and I cant seem to figure out why!
import React, { Component } from "react";
export class StepOne extends Component {
constructor(props) {
super(props);
this.state = {
box1: false,
box2: false,
box3: false,
};
}
handleChange = (evt) => {
const box = evt.target.name;
this.setState({ [box]: !this.state.box });
};
render() {
return (
<div>
<input type="checkbox" name="box1" onChange={this.handleChange} />
<input type="checkbox" name="box2" onChange={this.handleChange} />
<input type="checkbox" name="box3" onChange={this.handleChange} />
</div>
);
}
}

When using a dot notation, compiler tries to look for a field called box in the state and since it doesn't exist - you are not getting any result when toggling the checkbox.
Use a bracket notation instead:
handleChange = (evt) => {
const box = evt.target.name;
this.setState((prevState) => ({
[box]: !prevState[box],
}));
};
Note: Consider using a function when setting the state to be sure that you refer to the relevant state.

you just have to make a change in your handleChange function as below:
handleChange = (evt) => {
const box = evt.target.name;
this.setState([box]: evt.target.checked);
};
this will give the state the same checked state of the checkbox (if the checkbox is checked, the state will be set to true, if it is not, it will be set to false).
If you have any other problem with this solution you can comment so i can help you more

Related

Trouble updating radio button state value in parent component from child element

I'm currently working a a multipage checklist app to make a common checklist procedure more efficient.
my parent component called MainForm has all of the states for my app. In my first child element, I had to fill some text inputs. The states are updating and saving as planned. My second page (or other child element) was the portion where my checklist would begin. The issue is my app is rending, but the radiobutton value isn't being sent to my state. I'm also having an issue where I can select the 'yes' radio button and then the 'no' radio button, but I can't go from 'no' to 'yes'. radioGroup21 is the radio group that's giving me problem. All other states are working.
I'm getting an error in my console that says:
"Checkbox contains an input of type radio with both value and defaultValue props. Input elements must be either controlled or uncontrolled (specify either the value prop, or the defaultValue prop, but not both). Decide between using a controlled or uncontrolled input element and remove one of these props.
I've tried removing the value tag and the defaultValue line in my Radio elements, but no luck. I've tried creating constructor(props) in my parent element but I still kept having issues."
So far I've tried removing the defaultValue in my radio button and after I tried removing the value line. Unfortunately I this did not help.
I also read about controlled and uncontrolled inputs. I've tried changing my parent components state to put them in a constructor(props) bracket. But no luck.
I also tried to not use the handleChange function and use the setState function with values of {radioButton21 === 'yes'} but that didn't work.
//Parent Component
Class MainForm extends Component {
state = {
step: 1,
projectNumber: '',
projectName: '',
numberOfSystems: '',
buildSheet: '',
controlPhilosophy: '',
projectLayoutDrawing: '',
projSoftwareValidation: '',
CppDrawing: '',
radioGroup21: '',
}
nextStep = () => {
const { step } = this.state
this.setState({
step : step + 1
})
}
prevStep = () => {
const { step } = this.state
this.setState({
step : step - 1
})
}
handleChange = input => event => {
this.setState({ [input] : event.target.value })
}
render(){
const {step} = this.state;
const { projectNumber, projectName, numberOfSystems, buildSheet , controlPhilosophy, projectLayoutDrawing, projSoftwareValidation, CppDrawing, radioGroup21 } = this.state;
const values = { projectNumber, projectName, numberOfSystems, buildSheet, controlPhilosophy, projectLayoutDrawing, projSoftwareValidation, CppDrawing, radioGroup21 };
switch(step) {
case 1:
return <ProjectInfo
nextStep={this.nextStep}
handleChange = {this.handleChange}
values={values}
/>
case 2:
return <PrelimInspection
nextStep={this.nextStep}
prevStep={this.prevStep}
handleChange = {this.handleChange}
values={values}
/>
export default MainForm;
-----------------------------------
//Child Component
import React, { Component } from 'react';
import { Form, Button, Radio } from 'semantic-ui-react';
import { throws } from 'assert';
class PrelimInspection extends Component{
saveAndContinue = (e) => {
e.preventDefault();
this.props.nextStep();
}
back = (e) => {
e.preventDefault();
this.props.prevStep();
}
render(){
const { values } = this.props
return(
<Form color='blue' >
<h1 className="ui centered">System Installation</h1>
<Form.Field inline>
<Form.Field>System Properly Supported</Form.Field>
<Radio
label = {'Yes'}
name = {'radio21'}
value = {'Yes'}
onChange={this.props.handleChange('radioGroup21')}
defaultValue={values.radioGroup21}
/>
<Radio
label = {'No'}
name = {'radio21'}
value = {'No'}
onChange={this.props.handleChange('radioGroup21')}
defaultValue={values.radioGroup21}
/>
</Form.Field>
<Button onClick={this.back}>Back</Button>
<Button onClick={this.saveAndContinue}>Save And Continue </Button>
</Form>
)
}
}
export default PrelimInspection
The app is rendering and the layout is correct. Unfortunately the state values aren't being sent to the parent state.
I checked the documentation https://react.semantic-ui.com/addons/radio/#types-radio-group and I have found few things you missed:
1.) Radio component asked the checked props (but you did not supply it).
2.) Which then requires you to pass the value, in your case it should come from the parent component:
<PrelimInspection
valueFromParent={this.state["radioGroup21"]}
nextStep={this.nextStep}
handleChange={this.handleChange}
values={values}
/>
so in your Child Component' render, take the value:
render() {
const { values, valueFromParent } = this.props;
...
3.) Radio's onChange value is passed as the second param (obj.value).
<Radio
label={'Yes'}
name={'radio21'}
value={"Yes"}
checked={valueFromParent === 'Yes'}
onChange={this.props.handleChange("radioGroup21")}
...
/>
So you can take the selected value like this:
// MainForm
handleChange = input => (event, obj) => { // obj is the second param
console.log("sendin here", input, obj.value);
this.setState({ [input]: obj.value });
};

google-maps-react infoWindow onChange issues

How to add a button in infowindow with google-maps-react?
Hello, I'm writing a React app, I was having an issue with changing state inside the InfoWindow from google-maps-react, the solution above helped me get through that hurdle.
Right now however, I'm having an issue with wanting to edit the content inside my InfoWindowEx component. Using the method above I am able to change the state of a text box inside the InfoWindowEx, however, when I click on the text box and I type it will let me type 1 letter and then I will have to click the text box again if I want to type the next letter, etc. I think this issue has to do with state.
I don't know if there is a solution to this, I have been trying a lot of different things, but hopefully someone can help me know what is going on.
Here is my InfoWindowEx component:
<InfoWindowEx
key={currentInfoWindow.id}
id={currentInfoWindow.id}
marker={this.state.activeMarker}
visible={this.state.showingInfoWindow}
selectedPlace={this.state.selectedPlace}
onInfoWindowClose={this.onInfoWindowClose}
>
<div >
{infoWindowEditBoxes}
{infoWindowContent}
</div>
</InfoWindowEx>
the Edit boxes are rendering in conditional statements here are they:
if (this.state.editButton) {
infoWindowEditBoxes = (
<div>
<input key={this.props.marker} id="editedName" type="text" placeholder="New Bathroom Name" onChange={this.handleTextBoxState}></input>
<input key={this.props.marker} id="editedLocationName" type="text" placeholder="New Bathroom Location" onChange={this.handleTextBoxState}></input>
<button onClick={() => this.handleSubmitChangesButtonState()}>Submit Changes</button>
</div>
);
}
else {
infoWindowEditBoxes = null
}
and here is my state change function:
handleTextBoxState = (evt) => {
const stateToChange = {}
stateToChange[evt.target.id] = evt.target.value
this.setState(stateToChange)
console.log(stateToChange)
}
Thanks in advance!
I believe component state is getting updated properly in your example, apparently this behavior is related with InfoWindowEx component itself. The way how it is implemented, setState() causes to a re-render InfoWindow component which leads to losing input focus.
You could consider the following updated version of component which prevents re-rendering of info window if it has been already opened:
export default class InfoWindowEx extends Component {
constructor(props) {
super(props);
this.state = {
isOpen: false
};
this.infoWindowRef = React.createRef();
this.containerElement = document.createElement(`div`);
}
componentDidUpdate(prevProps) {
if (this.props.children !== prevProps.children) {
ReactDOM.render(
React.Children.only(this.props.children),
this.containerElement
);
this.infoWindowRef.current.infowindow.setContent(this.containerElement);
this.setState({
isOpen: true
});
}
}
shouldComponentUpdate(nextProps, nextState) {
if (this.state.isOpen) {
return this.props.marker.position.toString() !== nextProps.marker.position.toString();
}
return true;
}
infoWindowClose(){
this.setState({
isOpen: false
});
}
render() {
return <InfoWindow onClose={this.infoWindowClose.bind(this)} ref={this.infoWindowRef} {...this.props} />;
}
}
Demo

React Parent component checkbox state updates with one step delay

I have a Parent component:
import React, { Component } from "react";
import { Button } from "./Button";
export class Dashboard extends Component {
constructor(props) {
super(props);
this.state = {
numbers: [],
disabled: false
};
this.setNum = this.setNum.bind(this);
}
setNum(num) {
if (!this.state.numbers.includes(num)) {
this.setState(prevState => ({
numbers: [...prevState.numbers, num]
}));
} else if (this.state.numbers.includes(num)) {
let nums = [...this.state.numbers];
let index = nums.indexOf(num);
nums.splice(index, 1);
this.setState({ numbers: nums });
console.log(this.state.numbers);
}
if (this.state.numbers.length >= 4) {
this.setState({ disabled: true });
} else if (this.state.numbers.length < 4) {
this.setState({ disabled: false });
}
}
render() {
return (
<div className="board-container">
<div className="board">
<div className="row">
<Button
id="1"
numbers={this.state.numbers}
onChange={this.setNum}
disabled={this.state.disabled}
/>
<Button
id="2"
numbers={this.state.numbers}
onChange={this.setNum}
disabled={this.state.disabled}
/>
<Button
id="3"
numbers={this.state.numbers}
onChange={this.setNum}
disabled={this.state.disabled}
/>
<Button
id="4"
numbers={this.state.numbers}
onChange={this.setNum}
disabled={this.state.disabled}
/>
</div>
</div>
</div>
);
}
}
... and a Child component:
import React, { Component } from "react";
export class Button extends Component {
constructor(props) {
super(props);
this.state = {
isChecked: false
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
this.setState({
isChecked: !this.state.isChecked
});
var num = e.target.value;
this.props.onChange(num);
}
render() {
const { isChecked } = this.state;
if (isChecked === true) {
var bgColor = "#f2355b";
} else {
bgColor = "#f7f7f7";
}
let disabled = this.props.disabled;
if (this.props.numbers.includes(this.props.id)) {
disabled = false;
}
return (
<div className="number-container" id="checkboxes">
<label
className={!isChecked && disabled === false ? "num" : "checked-num"}
style={{ backgroundColor: bgColor }}
>
{" "}
{this.props.id}
<input
type="checkbox"
name={this.props.id}
value={this.props.id}
id={this.props.id}
onChange={this.handleChange}
checked={isChecked}
disabled={disabled}
/>
</label>
</div>
);
}
}
Whenever any Button component is clicked, the Parent component gets the child Button's id value and puts it into its numbers state array. Whenever a Button is unchecked, the Parent updates is numbers state by removing the id of the child Button.
If my code is right, the expected behavior is whenever a Button checkbox is clicked, the Parent numbers state will be updated immediately (adding or removing a number). However, it always updates with one step lag behind.
I know, that the issue is dealing with the React states not being updated instantly, and I've checked similar issues on Stackoverflow. The problem is that I can't figure it out how to make this two components interact with each other in a proper way. What would be the solution for this issue?
Here are three screenshots from codesandbox
If you want to play with it please find the link https://codesandbox.io/s/w2q8ypnxjw
What I did was, I basically copied and pasted your code and updated setNum function to reflect the changes Think-Twice suggested
setNum(num) {
if (!this.state.numbers.includes(num)) {
this.setState(
prevState => ({
numbers: [...prevState.numbers, num]
}),
() => {
console.log("state logged inside if", this.state.numbers);
}
);
} else if (this.state.numbers.includes(num)) {
let nums = [...this.state.numbers];
let index = nums.indexOf(num);
nums.splice(index, 1);
this.setState({ numbers: nums }, () => {
console.log("state logged inside else if", this.state.numbers);
});
}
if (this.state.numbers.length >= 4) {
this.setState({ disabled: true });
} else if (this.state.numbers.length < 4) {
this.setState({ disabled: false });
}
}
So before going further let's quickly address a couple of things regarding to React and setState
As B12Toaster mentioned and provided a link which contains a
quote from official documentation
setState() does not always immediately update the component. It may
batch or defer the update until later.
Think-Twice's also points out that by stating
Basically setState is asynchronous in React. When you modify a value
using setState you will be able to see the updated value only in
render..
So if you want to see the immediate state change in a place which
you trigger setState, you can make use of a call back function as
such setState(updater[, callback])
There are two approaches when it comes to and updater with setState,
you could either pass an object, or you could pass a function So in
Think-Twice's example, an object is passed as an updater
this.setState({ numbers: nums } //updater, () => {
console.log(this.state.numbers); //this will print the updated value here
});
When a function is used as an updater (in your setNum function you
already do that), the callback function can be utilized like below
if (!this.state.numbers.includes(num)) {
this.setState(
prevState => ({
numbers: [...prevState.numbers, num]
}),
() => {
console.log("state logged inside if", this.state.numbers);
}
);
}
Your current implementation and communication structure seems fine. It is actually called Lifting State Up which is recommended also by official documentation.
Basically you store the state of array numbers in a parent component (which can be considered as the source of truth) and you pass the method that changes the state as a prop to it's child component.
In the codesandbox link I provided, the functionalities works the way I expect (at least this is what I expect from your code)
Basically setState is asynchronous in React. When you modify a value using setState you will be able to see the updated value only in render. But to see updated state value immediately you need to do something like below
this.setState({ numbers: nums }, () => {
console.log(this.state.numbers); //this will print the updated value here
});

ReactJS, Checkbox doesn't

I Have a ReactJS checkbox component. When onChange is called I can log the new state and see it changing, but it never actually re-renders the checkbox into the new state. So the ADD_ID action is never called. See code below:
class CheckBox extends React.Component {
constructor(props) {
super(props)
this.state = {
checked: true
}
}
changing = (e) => {
this.setState(prevState => ({checked: !prevState.checked}), () => {
console.log(this.state.checked); // false
this.state.checked
? store.dispatch({ type: 'ADD_ID', id: this.props.id })
: store.dispatch({ type: 'REMOVE_ID', id: this.props.id });
});
}
render() {
return (
<label>
Include
<input onChange={this.changing} checked={this.state.checked} type='checkbox'/>
</label>
)
}
}
Is there a lifecycle hook that I have to call? I was under the impression that the component would re-render when either it's props or state changes, in this case, as shown by the console.log(this.state.checked), the state has changed, but the component doesn't re-render.
The event has already a checked property for you. You're doing it in a way that's a bit weird.
Change your function to something like:
handleChange = (e) => {
const isChecked = e.target.checked
if(isChecked){
store.dispatch({type:'ADD_ID', id:this.props.id})
} else {
store.dispatch({type:'REMOVE_ID', id:this.props.id})
}
this.setState(checked: isChecked)
}
It's however still strange that you're using at the same time internal state and Redux. You may want to rethink your approach here.
React docs on forms, which I recommend you to read in 5 min:
https://reactjs.org/docs/forms.html

React: programmatically change component value doesn't trigger onChange event

I have one select field inside a React component, it value it's set through the component state an has an function attached to the onChange event. If I change the select field value manually, the onChange event it's triggered, but if I change it by changing the state value from another function it is not. It's there a way to trigger the event programmatically?
Edit:
Below is a basic example on what I need to achieve. The idea is that when the handleChange1() changes the value of state.val2 (and therefore change the option selected on the second select field) the handleChange2() is also triggered so the synthetic event is passed to the parent function (in the actual code, the select fields are another components):
class Component extends React.Component {
state = {
val1: 1,
val2: 1,
}
handleChange1 = (event) => {
const val2 = event.target.value === 3 ? 1 : null;
this.setState({
val1: event.target.value,
});
if (event.target.value === 3) {
this.setState({
val2: 1,
});
}
this.props.parentFunction(event);
}
handleChange2 = (event) => {
this.setState({
val2: event.target.value,
});
this.props.parentFunction(event);
}
render() {
return (
<div>
<select value={val1} onChange={this.handleChange1}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<select value={val2} onChange={this.handleChange2}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
</div>
);
}
};
Yes, there is a way! React has logic that prevents onChange from firing when an input's value is set programmatically, but it can be worked around.
Instead of:
input.value = 'foo';
Do this:
const setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
setter.call(input, 'foo');
input.dispatchEvent(new Event('input', { bubbles: true }));
Check out this article for the full explanation.
In case it's not clear, the value of input is the DOM element that you would get from a ref. Example:
function SomeComponent({ onChange }) {
const ref = useRef();
useEffect(() => {
setInterval(() => {
const input = ref.current;
const setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
setter.call(input, new Date().toISOString());
input.dispatchEvent(new Event('input', { bubbles: true }));
}, 10000);
});
return <input type="text" ref="ref" onChange={onChange} />;
}
This component would update its input with a date string & trigger the onChange callback with the new value every 10 seconds.
You should wrap your input in a dedicated component to customize the desired behavior. Something like :
class Input extends React.Component {
constructor(props){
super(props);
this.state = {
value: props.value
}
}
componentWillReceiveProps(nextProps) {
this.setState({
value: nextProps.value,
});
this.props.onChange(nextProps.value);
}
updateValue(ev) {
this.setState({
value: ev.target.value,
});
this.props.onChange(ev.target.value);
}
render() {
return (
<input
onChange={this.updateValue.bind(this)}
value={this.state.value}
{...this.props}
/>
)
}
}
and use it like:
<Input value="test" onChange={someAction} />
note that because your input is in a controlled state, value must never be null nor undefined.
I had the same problem and fortunately #gouroujo's answer works for me, but as I checked the documentation its name has changed to UNSAFE_componentWillReceiveProps() and docs say:
Note
This lifecycle was previously named componentWillReceiveProps. That name will continue to work until version 17. Use the rename-unsafe-lifecycles codemod to automatically update your components.
More details about UNSAFE_componentWillReceiveProps()
Another solution is to set a key on the component so that react can
create a new component instance rather than update the current one.
More details about Fully uncontrolled component with a key

Resources