How to handle onChange event that sets the state in React? - reactjs

I am learning React and in the below code I get ...cannot update during an existing state transition.... While looking to fix it, I read in SO that setState should not be used within render() as it calls render() repeatedly resulting in infinite loop. But I dont know how to fix it.
import React from 'react';
import ReactDOM from 'react-dom';
export default class CheckBox extends React.Component{
constructor() {
super();
this.state = {isChecked: false};
}
handleChecked () {
this.setState({isChecked: !this.state.isChecked});
}
render(){
var txt;
if (this.state.isChecked) {
txt = 'checked'
} else {
txt = 'unchecked'
}
return(
<div>
<input type="checkbox" onChange={this.handleChecked()}/>
<p>This box is {txt}</p>
</div>
);
}
}
ReactDOM.render(<CheckBox/>, document.getElementById('hello'));

You should pass to onChange reference to function but not call it., in your example you are calling handleChecked(because there is () after function) and result pass to onChange however result in this case will be undefined so onChange looks like onChange={ undefined }. Also, you can't set state during the initial render, but you are trying to do it with this.handleChecked() which contains this.setState.
export default class CheckBox extends React.Component{
constructor() {
super();
this.state = {isChecked: false};
// set this (with .bind),
// because you need get methods from CheckBox, like .setState
this.handleChecked = this.handleChecked.bind(this);
}
handleChecked () {
this.setState({isChecked: !this.state.isChecked});
}
render(){
var txt;
if (this.state.isChecked) {
txt = 'checked'
} else {
txt = 'unchecked'
}
// remove () after handleChecked because you need pass
// reference to function
// also add return statement before <div>
return <div>
<input type="checkbox" onChange={ this.handleChecked }/>
<p>This box is {txt}</p>
</div>
}
}
Example

Related

State Variable assigned to props data model undefined inside setState React

I have passed a data model as props from Parent Component (MinorStructures) to Child component (PhotoGallery).
The parent Component looks like the following:
export default class MinorStructures extends Component {
constructor(props)
{
super(props);
// This is a super data model class, its main function is to collect
// data from all the children components.
this.state = {
MinorStructures: {
layer: 3,
layerName: 'MinorStructures',
layerId: -1,
agencyId: -1, //to be determined later
galleryModel:{
selectedFile: null
}
}
};
this.panes = [
{
menuItem: 'Photo Gallery', render: () =>
<Tab.Pane>
<PhotoGallery triggerNeedSave={this.props.triggerNeedSave}
disabled={this.props.disabled}
loggedIn = {this.props.loggedin}
minorModel={this.state.MinorStructures}/>
</Tab.Pane>
},
];
}
}
I have removed few codes from the parent class which is not necessary for this problem.
The Child Component is like the following:
export default class PhotoGallery extends Component{
constructor(props) {
super(props)
const {minorModel} = this.props
this.state={
cameraOpen: false,
photoModel: minorModel.galleryModel
}
console.log("Constructor State ", this.state)
}
handleChange = e =>{
this.props.triggerNeedSave();
this.setState({[photoModel.selectedFile]:e.target.files[0]})
console.log(this.state)
}
render() {
const uploadClick = e => {
hiddenFileInput.current.click();
};
return (
<div>
{!this.state.cameraOpen && <Button size='tiny' onClick={uploadClick}
color='brown'
disabled ={this.props.disabled}>Upload Photos from Device</Button>}
<input id="photo" name="selectedFile" type="file"
onChange={this.handleChange}
ref={hiddenFileInput} style={{display:'none'}} />
<Button size='tiny' onClick={checkModel} color='brown'
disabled ={this.props.disabled}>
Click To Check
</Button>
</div>
);
};
return (
<div id="root">
<Gallery />
</div>
)}
}
In the state of PhotoGallery class I have a photoModel that takes the data model from MinorStructures as props. When I select a picture and do setState in the handleChange method of PhotoGallery class it says photoModel is not defined. But I have defined that variable in the state which stores data model passed as props from MinorStructures.
You are using dynamic keys when changing state with [] to access properties on this.state
this.setState({[photoModel.selectedFile]:e.target.files[0]})
The correct way would be to change state with
this.setState({photoModel.selectedFile:e.target.files[0]})
I set a name attribute for the file type input html tag and then used the below code to set the value
this.setState({ [name]: e.target.files[0] });

ReactJS - setState() is not updating

I have a single page create-react-app project and the issue I am having is that the state is not updating. I have cut some of my code out just to make the example a bit shorter and easier to follow. The function where setState() is called is in form_change().
My goal here is to change the color of the text when there is an error, but even {this.state.test} isn't updating. I have tried putting console.log()s in various locations to get around the async nature of setState, but unfortunately they seem to show that state is never updated. It has been a while since I have used React, so there is a chance I am doing something very silly!
Thanks in advance for your help.
Here is my code:
import React,{Component} from 'react';
import './App.css';
import Dropdown from 'react-dropdown'
import classes from './classes.module.css'
import firebase from 'firebase';
import 'react-dropdown/style.css';
const axios = require('axios');
class App extends Component {
render(){
const error_empty = (param)=>{
if (this.state.error===undefined){
return false
}
else{
if (this.state.errors.find(el => el === param) === undefined){
return false
}
else return true
}
}
const form_change = (event, param)=>{
let form={...this.state.form};
form[param]=event.target.value;
let errors =verified(form);
console.log(form); //as expected
console.log(errors); //as expected
//works up til here. setState not updating for some reason.
this.setState({form:form,errors:errors,test:'Hello World'})
}
const verified = (data)=>{
let errors = [];
let form = data;
errors.push('ean')
return errors}
this.state = {
example:['abc'],
form:{
example:"abc"
}
}
return (
<div className="App">
<header className="App-header">
<div className={classes.button_holder}>
<div className={classes.page_button} onClick={()=>{null}}>
{this.state.test}
</div>
</div>
<div className={classes.user_form}>User Update Form
<div>
<input className={classes.input_text} style={{color: error_empty()?'red':'black'}} value={this.state.form.ean} onChange={(event)=>{form_change(event,'ean')}} placeholder={"EAN"}></input>
</div>
</div>
</header>
</div>
);
}
}
export default App;
Move initialization of state outside render function this.state.
Initialise your state in constructor
Don't update your state (this.setState) in render because this will lead to infinitive loop.
Move your functions error_empty(), form_change() and verified() outside the render.
To call function onChange use this
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
example:['abc'],
form:{
example:"abc"
}
error: ""
}
}
error_empty = (param) => {
if(this.state.error===undefined) {
return false
}
else {
if(this.state.errors.find(el => el === param) === undefined) {
return false
}
return true
}
}
...
render() {
...
<input ... onChange={(event) => {this.form_change(event,'ean')}}/>
...
}
}

Render child component in parent after re-rendering sibling component

I have a parent component housing two children components(AddPersonForm and PeopleList). When I submit a name via the AddPersonForm, I expect it to be rendered in the PeopleList component, but it doesn't.
Here is my AddPersonForm:
class AddPersonForm extends React.Component {
state = {
person: ""
}
handleChange = (e) => this.setState({person: e.target.value});
handleSubmit = (e) => {
if(this.state.person != '') {
this.props.parentMethod(this.state.person);
this.setState({person: ""});
}
e.preventDefault();
}
render() {
return (
<form onSubmit={this. handleSubmit}>
<input type="text" placeholder="Add new contact" onChange={this.handleChange} value={this.state.person} />
<button type="submit">Add</button>
</form>
);
}
My PeopleList component:
class PeopleList extends React.Component {
constructor(props) {
super(props);
const arr = this.props.data;
this.state = {
listItems: arr.map((val, index) => <li key={index}>{val}</li> );
}
}
render() {
return <ul>{this.state.listItems}</ul>;
}
}
Now the parent component, ContactManager:
class ContactManager extends React.Component {
state = {
contacts: this.props.data
}
addPerson = (name) => {
this.setState({contacts: [... this.state.contacts, name]});
render() {
return (
<div>
<AddPersonForm parentMethod={this. addPerson}×/>
<PeopleList data={this.state.contacts} />
</div>
);
Please what I'm I doing wrong, or not doing?
The issue is in your PeopleList component. The state object which renders your list is created in the constructor when the component mounts, but you have no way of updating it when it recieves new values. It will always give you the initial value.
You could introduce a lifecycle method, componentDidUpdate, which would allow you to compare the previous props to the new props when they arrive, and update the state accordingly. I would recommend you not do this for two reasons:
Storing props directly in a components state is not good practice. You are just creating a copy of the state in the component above and that creates opportunities for confusion and stale values when one of them updates. Ideally, each piece of data should live in only one place.
If all PeopleList is doing is rendering your data, then it doesn't need any state at all. It can act as a display component that maps your props in place and doesn't have to worry about updating itself or managing its own data. This would actually make it a good candidate for conversion into a functional component.
class PeopleList extends React.Component {
render() {
return (
<ul>
{this.props.data.map((val, index) => (
<li key={index}>{val}</li>
))}
</ul>
);
}
}
You are initializing PeopleList with props when its created and mounted but then you are not using new values of props for updating it.
To fix your issue use current value of prop when rendering:
class PeopleList extends React.Component {
render() {
return <ul>{ this.props.data.map((val, index) => <li key={index}>{val}</li>) }</ul>;
}
}

Multiple call on same event

I am calling two functions on the same event onChange
but the seconde one dosen't exucute this.updateValue
the value of the input doesn't change
----> but if we remove the first call and we change it to
---> onChange={this.updateValue}: the value on the input changes , it apear that there is a probleme while calling two function at once
import React from 'react'
class SearchBar extends React.Component
{
constructor(props)
{
super(props)
this.state = {input_value : ''}
}
updateValue = (event) =>
{
this.setState({input_value : event.target.value})
}
render()
{
return(
<React.Fragment>
<input
type="text"
value={this.state.input_value}
onChange={() => (this.props.toChild(this.state.input_value,this.updateValue))}
/>
</React.Fragment>
)
}
}
export default SearchBar
One function will be executed on onChange. You can do following
// prop to input in render
onChange={this.updateValue}
// In function
updateValue = (event) =>
{
this.props.toChild(event.target.value)
this.setState({input_value : event.target.value})
}

Uncaught TypeError: Cannot read property 'state' of undefined - React

I can not figure out if I am missing something small here?
Just trying to get a grasp on how state works with React.
Just creating a small check box that changes text on check/uncheck.
import React from 'react';
export default class Basic extends React.Component {
constructor(props) {
super(props);
this.state = {
checked: true
};
}
handleCheck() {
this.setState = ({
checked: !this.state.checked
});
}
render() {
var msg;
if (this.state.checked) {
msg = 'checked'
} else {
msg = 'unchecked'
}
return (
<div>
<input type="checkbox" onChange={this.handleCheck} defaultChecked={this.state.checked} />
<h3>Checkbox is {msg}</h3>
</div>
);
}
}
Changes:
1. You forgot to bind the onChange method, either use this:
onChange={this.handleCheck.bind(this)}
or define the binding in the constructor:
this.handleCheck = this.handleCheck.bind(this)
2. You used setState in a wrong way, setState is a method you need to call it.
Instead of: this.setState = ({})
it should be: this.setState({})
Binding your handleCheck function in the constructor:
constructor(props) {
super(props);
this.state = {
checked: true
};
this.handleCheck = this.handleCheck.bind(this);
}

Resources