How to fix setState() gives the opposite value of checkbox? - reactjs

This is my class component:
import React from 'react';
import { Jumbotron, Container, Label } from 'reactstrap';
import './Css.css';
class Settings extends React.Component {
state = {
isChecked: false,
}
isCheck(e) {
this.setState({
isChecked: e.target.checked
})
console.log(e.target.checked)
console.log(this.state.isChecked)
}
render() {
return (
<div className="margin">
<Jumbotron fluid>
<Container fluid>
<h1 className="display-3">Settings</h1>
<div className="groupbox">
<Label className="lead">This is a checkbox</Label>
<input class="apple-switch" type="checkbox" onChange={e => this.isCheck(e)} />
</div>
</Container>
</Jumbotron>
</div>
);
}
}
export default Settings;
The problem that I have is that the values that I get in the console are inversed.
So when the checkbox is unchecked e.target.checked is false but this.state.isChecked is true, and when the checkbox is checked e.target.checked is true and this.state.isChecked is false.
I don't know why I'm facing this issue.
Is there anything that I did wrong?
I tried also to store the e.target.checked to a const checked and then pass it to the setState() but it gives me the same thing.
Any help is appreciated.

The problem is purely a logging one. There is a delay between setting the value and seeing it updated on state. Try:
isCheck(e) {
console.log(e.target.checked);
this.setState({
isChecked: e.target.checked
}, () => {
console.log(this.state.isChecked);
});
}
If you need to do something after state has been updated, then put it into a callback function inside the setState call, as above. The reason for this is that setState calls render (that is its main purpose), and React will do that asynchronously, when it's ready.

According to the doc
Think of setState() as a request rather than an immediate command to update the component
setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall. Instead, use componentDidUpdate or a setState callback (setState(updater, callback)), either of which are guaranteed to fire after the update has been applied
So setState() here is asynchronous, so in your case, to see the effect, just use a callback (here I use arrow function to maintain the context of this)
isCheck(e) {
this.setState(
{
isChecked: e.target.checked,
},
() => {
console.log(e.target.checked)
console.log(this.state.isChecked)
}
)
}

Related

How to conditionally render component in <Field> of redux-form

In my code, was trying to render a <DatePicker> in <Field> component of Redux Form, via a function called renderDatePicker(). This function is linked to handleClick() function where the state variable isOpen is set to true.
So ideally, onClick should render the <DatePicker> as it is set to visible. But code doesn't update anything. Where am I doing wrong here?
Note: Rendering <DatePicker> alone directly without the help of <Field component=...>, works fine.
For debugging, complete code is in CodeSandbox,
SimpleForm.js
import React from "react";
import { reduxForm } from "redux-form";
import { Field } from "redux-form";
import DatePicker from "react-mobile-datepicker";
class SimpleForm extends React.Component {
constructor(props) {
super(props);
this.state = {
time: new Date(),
isOpen: false
};
this.handleClick = this.handleClick.bind(this);
}
renderDatePicker = () => (
<DatePicker
value={this.state.time}
isOpen={this.state.isOpen}
onSelect={this.handleSelect}
onCancel={this.handleCancel}
/>
);
handleClick() {
this.setState({ isOpen: true }, function() {
console.log(this.state.isOpen);
});
}
handleCancel = () => {
this.setState({ isOpen: false });
};
handleSelect = time => {
this.setState({ time, isOpen: false });
};
render() {
return (
<div>
<button className="select-btn" onClick={this.handleClick}>
select time
</button>
<Field
name="date"
type="date"
component={this.renderDatePicker}
label={"date"}
/>
</div>
);
}
}
export default reduxForm({
form: "simple" // a unique identifier for this form
})(SimpleForm);
The reason for this behavior lies in the implementation of react-form's Field component. It does a shallow compare of all its properties to decide whether it should rerender. You can change your component's state as much as you like, the reference to this.renderDatePicker won't change.
Field passes properties including an onChange handler and the current value into the field's component stateless function call to notify of changes, but this doesn't really apply here because your toggle button is outside of the field.
So one option that comes to my mind is to move your button into the rendered field and then call onChange(!value).
The easier yet dirtier option would be to use an arrow function in your component property: component={() => this.renderDatePicker()} - this instance changes with every re-render of your SimpleForm (i.e. if the state changes), so it comes with a cost, but depending on the complexity of your application the cost is negligible. To mitigate the impact, you could implement shouldComponentUpdate (just like redux-form's Field does) to decide whether it should rerender or not, based on the current and next isOpen state.
Check this bit in redux-form for more details: https://github.com/erikras/redux-form/blob/master/src/createField.js#L44

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
});

ComponentWillReceiveProps not getting invoked

The code is as follows.
state = {
showSection: false,
selectedAccount: ''
};
The render method is defined as follows.
return (
<div>
<button onClick={this.selectAccount} className="btn btn-default">Select Account</button>
{
this.state.showSection &&
<div>
<AccountSelectionForm handleAccordionClick=
{this.handleAccordionClick}
selectedAccount={this.state.selectedAccount}
/>
</div>
}
</div>
)
selectAccount Method is defined as follows.
selectAccount = () => {
this.setState({selectedAccount: 'A123'})
}
AccountSelectionFormComponent
componentWillReceiveProps(nextProps) {
if(nextProps.selectedAccount) {
this.setState({
accountId: nextProps.selectedAccount
});
}
}
When the button is clicked, selectedAccount value is changed. The prop value also gets changed, since this is passed in the props.
In the AccountSelectionForm component i have written the componentWillReceiveProps method to set the selectedAccount in the state.
My assumption was that this would invoke componentWillReceiveProps method since the prop is changed.
The problem is that the componentWillReceiveProps method is not invoked.
Questions:
Is this issue due to AccountSelectionForm still not rendered in DOM(this.state.showSection is still false).
Any idea on how to fix this?

React: Why is element style not toggling with setState?

It's just a simple toggle mechanism:
Onclick of the element is to toggle a border color change by responding to state change. It changes the color once! But won't toggle back to original color.
(I've experimented with so many variations of the functionality, read/reread React docs on state, setState's asynchronous/batch change functionality, and combed SO again-and-again.)
Can someone help me find a solution?
Thanks in advance!
import React, { Component } from 'react';
class Button extends Component {
constructor(props) {
super(props);
this.state = {
active: false,
}
this.updateActive = this.updateActive.bind(this);
}
updateActive(){
this.setState(function(){
this.state.active = !this.state.active;
{ return this.state.active; }
});
}
render(){
return (
<div className="seq_btn" onClick={this.updateActive} style={ {borderColor: this.state.active ? 'black' : 'rgb(193, 255, 112)' }}></div>
)
}
}
export default Button;
Because your return syntax is incorrect:
this.setState(function(){
this.state.active = !this.state.active;
{ return this.state.active; }
});
This should be:
this.setState(function(){
return { active: !this.state.active };
});
However, you don't need to use the callback here at all. You should just setState with the new data.
this.setState({ active: !this.state.active });
As a matter of good habit, you should never mutate state in any form that isn't directly performed with setState
Even using
this.state.active = !this.state.active
is bad form and is most likely your issue.
Instead consider
this.setState({ active: !this.state.active });
Also understand that setState's can be batched for processing later, they are not always immediately executed.
setState() does not always immediately update the component. It may batch or defer the update until later.
https://reactjs.org/docs/react-component.html#setstate
As noted below, a functional component would serve the same purpose without the overhead of lifecycle methods
import React from "react";
const Button = ({ active, clickAction }) => (
<div onClick={clickAction} className={{ borderColor: active ? "green": "purple"}}>
</div>);
export default Button;

Calling addEventListener after setState seems to run event twice

If I add an event listener after calling setState, I would expect that event listener to only get called if I trigger that event again. However, from the example below, when I click on the div, the event listener is called after the state is changed.
It doesn't happen if I remove the addEventListener or if I call evt.stopPropagation() inside toggleOpen, but I'm wondering why the event listener is getting called if I'm setting it after thte state is changed.
Doesn't setState change the state asynchronously, implying that the callback would be called after the event propagates?
import React from 'react';
import classNames from 'classnames';
import css from './Dropdown.scss';
export class Dropdown extends React.Component {
constructor(props) {
super(props);
this.state = {
open: false,
}
}
toggleOpen = (evt) => {
window.removeEventListener('click', this.toggleOpen);
this.setState({
open: !this.state.open,
}, () => {
window.addEventListener('click', this.toggleOpen);
});
}
render() {
const dropdownContentClasses = classNames(css.dropdownContent, {
[css.dropdownContent_open]: this.state.open,
});
console.log(this.state.open)
return (
<div className={css.dropdownContainer}>
<div onClick={this.toggleOpen}>
{this.props.title}
</div>
<div className={dropdownContentClasses}>
{this.props.children}
</div>
</div>
)
}
}
2 things. First, from https://reactjs.org/docs/react-component.html#setstate you want to use the prevState property to calculate your new state:
toggleOpen = evt => {
this.setState((prevState, props) => {
return { open : ! prevState.open };
});
}
Second, it's better to add your event handler during the componentDidMount() phase, and remove it during componentWillUnmount() phase:
componentDidMount() {
window.addEventListener('click', this.toggleOpen);
}
componentWillUnmount() {
window.removeEventListener('click', this.toggleOpen);
}
It seems like by the time the propagation reaches the window, the listener is added and then calls the eventListener. Adding event.stopPropagation should solve it.
This is weird!
Adding event listener on document instead of window fixes the issue.
https://jsfiddle.net/y09h3nuf/
Instead of adding the window event on toggleOpen, i would suggest to add the event inside componentDidMount with a state.open check.
componentDidMount() {
window.addEventListener('click', (e) => {
this.state.open && this.toggleOpen(e);
});
}
I added a fiddle here: https://jsfiddle.net/69z2wepo/87489/

Resources