When to use 'componentDidUpdate' method? - reactjs

I wrote dozens of Reactjs files, but I never used the componentDidUpdate method.
Is there any typical example of when need to use this method?
I want a real-world example, not a simple demo.

A simple example would be an app that collects input data from the user and then uses Ajax to upload said data to a database. Here's a simplified example (haven't run it - may have syntax errors):
export default class Task extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
name: "",
age: "",
country: ""
};
}
componentDidUpdate() {
this._commitAutoSave();
}
_changeName = (e) => {
this.setState({name: e.target.value});
}
_changeAge = (e) => {
this.setState({age: e.target.value});
}
_changeCountry = (e) => {
this.setState({country: e.target.value});
}
_commitAutoSave = () => {
Ajax.postJSON('/someAPI/json/autosave', {
name: this.state.name,
age: this.state.age,
country: this.state.country
});
}
render() {
let {name, age, country} = this.state;
return (
<form>
<input type="text" value={name} onChange={this._changeName} />
<input type="text" value={age} onChange={this._changeAge} />
<input type="text" value={country} onChange={this._changeCountry} />
</form>
);
}
}
So whenever the component has a state change it will autosave the data. There are other ways to implement it too. The componentDidUpdate is particularly useful when an operation needs to happen after the DOM is updated and the update queue is emptied. It's probably most useful on complex renders and state or DOM changes or when you need something to be the absolutely last thing to be executed.
The example above is rather simple though, but probably proves the point. An improvement could be to limit the amount of times the autosave can execute (e.g max every 10 seconds) because right now it will run on every key-stroke.
I made a demo on this fiddle as well to demonstrate.
For more info, refer to the official docs:
componentDidUpdate() is invoked immediately after updating occurs. This method is not called for the initial render.
Use this as an opportunity to operate on the DOM when the component has been updated. This is also a good place to do network requests as long as you compare the current props to previous props (e.g. a network request may not be necessary if the props have not changed).

Sometimes you might add a state value from props in constructor or componentDidMount, you might need to call setState when the props changed but the component has already mounted so componentDidMount will not execute and neither will constructor; in this particular case, you can use componentDidUpdate since the props have changed, you can call setState in componentDidUpdate with new props.

This lifecycle method is invoked as soon as the updating happens. The most common use case for the componentDidUpdate() method is updating the DOM in response to prop or state changes.
You can call setState() in this lifecycle, but keep in mind that you will need to wrap it in a condition to check for state or prop changes from previous state. Incorrect usage of setState() can lead to an infinite loop.
Take a look at the example below that shows a typical usage example of this lifecycle method.
componentDidUpdate(prevProps) {
//Typical usage, don't forget to compare the props
if (this.props.userName !== prevProps.userName) {
this.fetchData(this.props.userName);
}
}
Notice in the above example that we are comparing the current props to the previous props. This is to check if there has been a change in props from what it currently is. In this case, there won’t be a need to make the API call if the props did not change.
For more info, refer to the official docs:

componentDidUpdate(prevProps){
if (this.state.authToken==null&&prevProps.authToken==null) {
AccountKit.getCurrentAccessToken()
.then(token => {
if (token) {
AccountKit.getCurrentAccount().then(account => {
this.setState({
authToken: token,
loggedAccount: account
});
});
} else {
console.log("No user account logged");
}
})
.catch(e => console.log("Failed to get current access token", e));
}
}

I have used componentDidUpdate() in highchart.
Here is a simple example of this component.
import React, { PropTypes, Component } from 'react';
window.Highcharts = require('highcharts');
export default class Chartline extends React.Component {
constructor(props) {
super(props);
this.state = {
chart: ''
};
}
public componentDidUpdate() {
// console.log(this.props.candidate, 'this.props.candidate')
if (this.props.category) {
const category = this.props.category ? this.props.category : {};
console.log('category', category);
window.Highcharts.chart('jobcontainer_' + category._id, {
title: {
text: ''
},
plotOptions: {
series: {
cursor: 'pointer'
}
},
chart: {
defaultSeriesType: 'spline'
},
xAxis: {
// categories: candidate.dateArr,
categories: ['Day1', 'Day2', 'Day3', 'Day4', 'Day5', 'Day6', 'Day7'],
showEmpty: true
},
labels: {
style: {
color: 'white',
fontSize: '25px',
fontFamily: 'SF UI Text'
}
},
series: [
{
name: 'Low',
color: '#9B260A',
data: category.lowcount
},
{
name: 'High',
color: '#0E5AAB',
data: category.highcount
},
{
name: 'Average',
color: '#12B499',
data: category.averagecount
}
]
});
}
}
public render() {
const category = this.props.category ? this.props.category : {};
console.log('render category', category);
return <div id={'jobcontainer_' + category._id} style={{ maxWidth: '400px', height: '180px' }} />;
}
}

When something in the state has changed and you need to call a side effect (like a request to api - get, put, post, delete). So you need to call componentDidUpdate() because componentDidMount() is already called.
After calling side effect in componentDidUpdate(), you can set the state to new value based on the response data in the then((response) => this.setState({newValue: "here"})).
Please make sure that you need to check prevProps or prevState to avoid infinite loop because when setting state to a new value, the componentDidUpdate() will call again.
There are 2 places to call a side effect for best practice - componentDidMount() and componentDidUpdate()

#K.tin, if you call setState in componentDidUpdate(), would that cause the same data to be fetched again?
For example, [id, data_for_id] are states, and id can be changed by a click counter and data_for_id is fetched from a web API.
Now we click to change Id by setState(), and componentDidUpdate() is executed, which fetches the data_for_id, we do setState for data_for_id, which will trigger another componentDidUpdate().
The first time componentDidUpdate is called, we have prevState.ID = 0 and state.ID=1, so componentDidUpdate is run. The second time we have prevState.ID = 1 and state.ID = 1, and componentDidUpdate can be avoid entirely, which perhaps could also be implemented with shouldComponentUpdate().
Still, this causes TWO rerenders, one for ID change and one for data_for_id change, Ideally, once we detect ID change, data_for_id should be fetched, and we should have [Id, data_for_id] state changed in a single shot, and the rerender happens only once for this change ID click.
So as a general rule, we should not do any setState in componentDidUpdate(), if the change of two or more state components are related, we should perform the changes together in one place and setState in a single shot.
This is just my reasoning. I am not a react guru, so please comment.

You should be careful when it's used. because it's making multiple API calls. Sometimes unlimited calls

componentDidUpdate should not have setState inside of it. you should use componentDidUpdate specially in manipulating the dom base on this.state values. CRUD or updating state values is not good inside of componentDidUpdate. Also dont fetching new data inside of componentDidUpdate as is can cause multiple http request on server and sometimes can cause laggy specially when the code is not structural or multiple use of setState.

Related

ReactJS: Deep nested state

Let's say I have this React component:
class SomeComponent extends Component {
constructor(props) {
super(props);
this.state = {
topObject: {
childObject1: {
grandChildObj1: {
attr1: this.props.val1,
attr2: this.props.val2
}
},
childProp: 1
},
topProp: 2
};
}
render() {
return (
<div>
<span>{this.state.topObject.childObject.grandChildObject.attr1}
</span>
</div>
// ...
)
}
changeDeepNestedStateValue(val) {
// need code here to change the state
// set topObj.childObject.grandChildObject.attr1
// to the 'val' argument
}
}
What code would I need inside the function 'changeDeepNestedStateValue' so that it changes the state immutably so that React detects the change and re-renders?
Are deep-nested state values a bad practice or anti-pattern? If so, is there an optimal structure to a state, a flat one maybe?
Deep nesting is not necessarily an anti-pattern but just makes your code harder to maintain and reason about. In order to trigger a re-render and update component state, all you have to do in the changeDeepNestedStateValue function is call this.setState({ topObject: {...} }) with whatever new state you want to update. Optionally, this.setState also takes a function that exposes the previous state of the component as seen below.
this.setState((prevState) => {
if (prevState.topProp === val) {
doSomething();
}
});
I'd recommend having a look at immer
https://github.com/mweststrate/immer
It provides a super easy way to work with nested objects in terms of immutablilty
But yes, flatter state with out of the box react state management is better practice

How to replace componentWillRecieveProps

I'm new to redux and followed this tutorial to create a simple blog app with react and redux. I've completed it, however I noticed that componentWillRecieveProps is being deprecated. I'm trying to replace it with more up-to-date code, but have been unable to understand how to do so. I've read this article about replacing ‘componentWillReceiveProps’ with ‘getDerivedStateFromProps’, but I don't think that this is the correct use for getDerivedStateFromProps as React's blog post on replacing one with the other describes.
My current componentWillRecieveProps code:
import axios from "axios";
import React from "react";
import { connect } from "react-redux";
class Form extends React.Component {
constructor(props) {
super(props);
this.state = {
title: "",
body: "",
author: ""
};
this.handleChangeField = this.handleChangeField.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
//This is deprecated
componentWillReceiveProps(nextProps) {
console.log(this);
if (nextProps.articleToEdit) {
this.setState({
title: nextProps.articleToEdit.title,
body: nextProps.articleToEdit.body,
author: nextProps.articleToEdit.author
});
}
}
handleSubmit() {
const { onSubmit, articleToEdit, onEdit } = this.props;
const { title, body, author } = this.state;
if (!articleToEdit) {
return axios
.post("http://localhost:8000/api/articles", {
title,
body,
author
})
.then(res => onSubmit(res.data))
.then(() => this.setState({ title: "", body: "", author: "" }));
} else {
return axios
.patch(
`http://localhost:8000/api/articles/${articleToEdit._id}`,
{
title,
body,
author
}
)
.then(res => onEdit(res.data))
.then(() => this.setState({ title: "", body: "", author: "" }));
}
}
handleChangeField(key, event) {
this.setState({
[key]: event.target.value
});
}
render() {
const { articleToEdit } = this.props;
const { title, body, author } = this.state;
return (
<div className="col-12 col-lg-6 offset-lg-3">
<input
onChange={ev => this.handleChangeField("title", ev)}
value={title}
className="form-control my-3"
placeholder="Article Title"
/>
<textarea
onChange={ev => this.handleChangeField("body", ev)}
className="form-control my-3"
placeholder="Article Body"
value={body}
/>
<input
onChange={ev => this.handleChangeField("author", ev)}
value={author}
className="form-control my-3"
placeholder="Article Author"
/>
<button
onClick={this.handleSubmit}
className="btn btn-primary float-right"
>
{articleToEdit ? "Update" : "Submit"}
</button>
</div>
);
}
}
const mapDispatchToProps = dispatch => ({
onSubmit: data => dispatch({ type: "SUBMIT_ARTICLE", data }),
onEdit: data => dispatch({ type: "EDIT_ARTICLE", data })
});
const mapStateToProps = state => ({
articleToEdit: state.home.articleToEdit
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(Form);
Full Repo
Codesandbox
If componentWillRecieveProps is being deprecated, how should I update my code?
Edit: using getDerivedStateFromProps, nextProps still has the previous values so when returning the object, state is set back to previous state, no updates are actually made.
Trying to use prevState values doesn't work in this case because the initial prevState values are all empty strings which are called and placed into state anytime the component renders, which occurs on initial page load, and when clicking the edit button.
static getDerivedStateFromProps(nextProps, prevState) {
if (
JSON.stringify(nextProps.articleToEdit) !==
JSON.stringify(prevState.articleToEdit)
) {
console.log(nextProps);
console.log(prevState);
return {
title: nextProps.articleToEdit.title,
body: nextProps.articleToEdit.body,
author: nextProps.articleToEdit.author
}; // <- is this actually equivalent to this.setState()?
} else {
return null;
}
}
componentWillReceiveProps() method is deprecated by introducing a new life cycle method called getDerivedStateFromProps().
Keep in mind that you should always compare current props with previous props like below and if they both are not same then do setState otherwise you will get into infinite setState warning
Replace below code in place of componentWillReceiveProps method
static getDerivedStateFromProps(nextProps, prevState) {
//Below I used JSON.stringify to compare current props with previous props of articleToEdit object
if (JSON.stringify(nextProps.articleToEdit) !== JSON.stringify(prevState.artileToEdit)){
return({
title: nextProps.articleToEdit.title,
body: nextProps.articleToEdit.body,
author: nextProps.articleToEdit.author
});// <- this is setState equivalent
}
return null;
}
Edit:
You cannot access this.props inside getDerivedStateFromProps function. If you want to access some previous props (like for comparison) inside the function, then you can mirror such values inside the state. Then you can compare the nextProps with the value stored in state like above
Check this thread for better understanding
https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html#updating-state-based-on-props
Alrighty. Here's a working example for your application. I completely rewrote its structure so that each component handles its own set of state and props. The components are also reusable so they can be placed wherever -- provided that it has required props.
Notes:
Don't mix .jsx and .js (see step 6 for fix)
Separate your container-components from your components. Also make reusable components -- this vastly simplifies your state and prop logic.
For your application, you can completely remove redux and instead allow your server to be the "source of truth". So instead of saving to redux state and manipulating redux state, you would simply make AJAX requests to leverage your API for CRUDing your articles (for example, form submission would send post request to API, which would add an article to the DB, then either redirect the user back to /articles or it could simply return all articles after it has posted and update ShowArticles state with new data). Up to you on how you want to handle that. But, with that said, the only time you really need redux is if you're sharing props from two separate heavily nested components.
If using redux, add a types and actions folder as shown in the example below. It makes your code easier to read and more modular.
Avoid using index.js unless the root folder only houses a single file. Otherwise, when the application breaks (and it will), it'll be a hassle to figure out which "index.js" broke if you have many.
The boilerplate you're using is pretty old and you're making your life harder by using it. I've created a MERN Fullstack Boilerplate that allows things like: fat arrow functions in class methods, scss/css node_module imports, component-level scss module imports, runs server and client simultaneously, has an error overlay, eslinting, and plenty more. I highly recommend porting what you have over to it.
Working example: https://codesandbox.io/s/4x4kxn9qxw (unfortunately, the create-react-app template doesn't allow scss module imports... oh well, you'll get the idea)
My advice would be to heed the warning that React is giving us by replacing this lifecycle method because of its misuse — they are telling us only do this if you really have to, because there is probably a better way (including the newer getDerivedStateFromProps).
From what I gather in this code, you are rendering a form, letting the user interact with it, but in some circumstance you replace the users' inputs with props (overwriting component state). Its easy to see why this is an anti-pattern but tricky to re-architect without knowing more about the app.
I'd suggest moving the inputs' values into the parent state (or creating a new parent), and change to value={props.title} etc. If there is a parent that controls a forms' state in any circumstance, you may as well hold form state there all the time.

React: State is set after api call, not before [duplicate]

This question already has answers here:
Why does calling react setState method not mutate the state immediately?
(9 answers)
The useState set method is not reflecting a change immediately
(15 answers)
Closed 8 months ago.
I would like to ask why my state is not changing when I do an onClick event. I've search a while ago that I need to bind the onClick function in constructor but still the state is not updating.
Here's my code:
import React from 'react';
import Grid from 'react-bootstrap/lib/Grid';
import Row from 'react-bootstrap/lib/Row';
import Col from 'react-bootstrap/lib/Col';
import BoardAddModal from 'components/board/BoardAddModal.jsx';
import style from 'styles/boarditem.css';
class BoardAdd extends React.Component {
constructor(props) {
super(props);
this.state = {
boardAddModalShow: false
};
this.openAddBoardModal = this.openAddBoardModal.bind(this);
}
openAddBoardModal() {
this.setState({ boardAddModalShow: true }); // set boardAddModalShow to true
/* After setting a new state it still returns a false value */
console.log(this.state.boardAddModalShow);
}
render() {
return (
<Col lg={3}>
<a href="javascript:;"
className={style.boardItemAdd}
onClick={this.openAddBoardModal}>
<div className={[style.boardItemContainer,
style.boardItemGray].join(' ')}>
Create New Board
</div>
</a>
</Col>
);
}
}
export default BoardAdd
Your state needs some time to mutate, and since console.log(this.state.boardAddModalShow) executes before the state mutates, you get the previous value as output. So you need to write the console in the callback to the setState function
openAddBoardModal() {
this.setState({ boardAddModalShow: true }, function () {
console.log(this.state.boardAddModalShow);
});
}
setState is asynchronous. It means you can’t call it on one line and assume the state has changed on the next.
According to React docs
setState() does not immediately mutate this.state but creates a
pending state transition. Accessing this.state after calling this
method can potentially return the existing value. There is no
guarantee of synchronous operation of calls to setState and calls may
be batched for performance gains.
Why would they make setState async
This is because setState alters the state and causes rerendering. This
can be an expensive operation and making it synchronous might leave
the browser unresponsive.
Thus the setState calls are asynchronous as well as batched for better
UI experience and performance.
Fortunately setState() takes a callback. And this is where we get updated state.
Consider this example.
this.setState({ name: "myname" }, () => {
//callback
console.log(this.state.name) // myname
});
So When callback fires, this.state is the updated state.
You can get mutated/updated data in callback.
For anyone trying to do this with hooks, you need useEffect.
function App() {
const [x, setX] = useState(5)
const [y, setY] = useState(15)
console.log("Element is rendered:", x, y)
// setting y does not trigger the effect
// the second argument is an array of dependencies
useEffect(() => console.log("re-render because x changed:", x), [x])
function handleXClick() {
console.log("x before setting:", x)
setX(10)
console.log("x in *line* after setting:", x)
}
return <>
<div> x is {x}. </div>
<button onClick={handleXClick}> set x to 10</button>
<div> y is {y}. </div>
<button onClick={() => setY(20)}> set y to 20</button>
</>
}
Output:
Element is rendered: 5 15
re-render because x changed: 5
(press x button)
x before setting: 5
x in *line* after setting: 5
Element is rendered: 10 15
re-render because x changed: 10
(press y button)
Element is rendered: 10 20
Live version
Since setSatate is a asynchronous function so you need to console the state as a callback like this.
openAddBoardModal(){
this.setState({ boardAddModalShow: true }, () => {
console.log(this.state.boardAddModalShow)
});
}
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. If you need to set the state based on the previous state, read about the updater argument below.
setState() will always lead to a re-render unless shouldComponentUpdate() returns false. If mutable objects are being used and conditional rendering logic cannot be implemented in shouldComponentUpdate(), calling setState() only when the new state differs from the previous state will avoid unnecessary re-renders.
The first argument is an updater function with the signature:
(state, props) => stateChange
state is a reference to the component state at the time the change is being applied. It should not be directly mutated. Instead, changes should be represented by building a new object based on the input from state and props. For instance, suppose we wanted to increment a value in state by props.step:
this.setState((state, props) => {
return {counter: state.counter + props.step};
});
Think of setState() as a request rather than an immediate command to
update the component. For better perceived performance, React may
delay it, and then update several components in a single pass. React
does not guarantee that the state changes are applied immediately.
Check this for more information.
In your case you have sent a request to update the state. It takes time for React to respond. If you try to immediately console.log the state, you will get the old value.
The above solutions don't work for useState hooks.
One can use the below code
setState((prevState) => {
console.log(boardAddModalShow)
// call functions
// fetch state using prevState and update
return { ...prevState, boardAddModalShow: true }
});
This callback is really messy. Just use async await instead:
async openAddBoardModal(){
await this.setState({ boardAddModalShow: true });
console.log(this.state.boardAddModalShow);
}
If you want to track the state is updating or not then the another way of doing the same thing is
_stateUpdated(){
console.log(this.state. boardAddModalShow);
}
openAddBoardModal(){
this.setState(
{boardAddModalShow: true},
this._stateUpdated.bind(this)
);
}
This way you can call the method "_stateUpdated" every time you try to update the state for debugging.
Although there are many good answers, if someone lands on this page searching for alternative to useState for implementing UI components like Navigation drawers which should be opened or closed based on user input, this answer would be helpful.
Though useState seems handy approach, the state is not set immediately and thus, your website or app looks laggy... And if your page is large enough, react is going to take long time to compute what all should be updated upon state change...
My suggestion is to use refs and directly manipulate the DOM when you want UI to change immediately in response to user action.
Using state for this purspose is really a bad idea in case of react.
setState() is asynchronous. The best way to verify if the state is updating would be in the componentDidUpdate() and not to put a console.log(this.state.boardAddModalShow) after this.setState({ boardAddModalShow: true }) .
according to React Docs
Think of setState() as a request rather than an immediate command to update the component. For better perceived performance, React may delay it, and then update several components in a single pass. React does not guarantee that the state changes are applied immediately
According to React Docs
React does not guarantee that the state changes are applied immediately.
This makes reading this.state right after calling setState() a potential pitfall and can potentially return the existing value due to async nature .
Instead, use componentDidUpdate or a setState callback that is executed right after setState operation is successful.Generally we recommend using componentDidUpdate() for such logic instead.
Example:
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
class App extends React.Component {
constructor() {
super();
this.state = {
counter: 1
};
}
componentDidUpdate() {
console.log("componentDidUpdate fired");
console.log("STATE", this.state);
}
updateState = () => {
this.setState(
(state, props) => {
return { counter: state.counter + 1 };
});
};
render() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<button onClick={this.updateState}>Update State</button>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
this.setState({
isMonthFee: !this.state.isMonthFee,
}, () => {
console.log(this.state.isMonthFee);
})
when i was running the code and checking my output at console it showing the that it is undefined.
After i search around and find something that worked for me.
componentDidUpdate(){}
I added this method in my code after constructor().
check out the life cycle of react native workflow.
https://images.app.goo.gl/BVRAi4ea2P4LchqJ8
Yes because setState is an asynchronous function. The best way to set state right after you write set state is by using Object.assign like this:
For eg you want to set a property isValid to true, do it like this
Object.assign(this.state, { isValid: true })
You can access updated state just after writing this line.

Updating store during rendering

I have a database as json file. During rendering I pick few objects at random and I want to update my global store when I map through the database, but calling dispatch in render() causes massive errors and I dont know how to proceed further.
Here is what I have without errors:
render() {
const fakePayload = this.props.fakePayload;
const rngPayloadId = Math.floor(Math.random() * 4);
const payload = fakePayload.map(payload => {
if (payload.payloadId === rngPayloadId) {
return payload.drugsId.map(id => {
return <tr>
<td> {id}</td>
<td>{drugs[id].name}</td>
<td><input value={undefined} type="number" name="price" /></td>
<td><button >Send</button></td>
<td><button>X</button></td>
</tr>
})
}
})
return (
<tbody>{payload}</tbody>
)
And what I would want to do is something like:
return payload.drugsId.map(id => {
this.props.dispatch(setId(randomNumber, id)
If it matters action looks like this:
export const setId = (id, drugId) => ({
type: 'SET_ID',
renderedDrugs: {
id,
drugId
}
})
What do I need to learn / do to do that?
Perhaps it is because you are dispatching an action in render. Have you tried dispatching the action on componentDidMount()?
Based on the comment:
constructor(props){
super(props);
this.state = {
rngId: 0
};
}
render(){
this.setState({rngId: Math.floor(Math.random() * 4)});
...
}
componentDidMount(){
this.props.dispatch(....);
}
Lifecycle events are a core part of React. You absolutely have to read up and understand it to a certain competency in order to use Redux. You cannot use an action inside render because an action updates your store, which updates the component, which calls render, which would also call your action, which updates your component, which calls render... I'm sure you see where this is going.
You are looking for either componentWillMount or componentDidMount. Functions inside these methods will be called once and will never call itself again while the component is mounted. Refer to the Facebook React docs on lifecycle events or other resources, like tutorials.
edit to answer your comment's question. A common paradigm for controlling rendering changes will the component is updated is to use a boolean (true/false) ternary statement, to show element or hide it (a simplification, but it works for now.) You'd be looking for something like this.
class SampleComponent {
constructor() {
this.state = {
show: false
}
}
componentDidMount() {
this.setState({show: true})
}
render() {
return (
<div>
{ this.state.show ? <div>mounted</div> : null }
</div>
)
}
There would be almost zero use cases to do something like this. componentDidMount occurs so quickly upon component mounting that you'd change this.state.show to true instantly. The html elements you were hiding using state would show as if they were never controlled with state to begin with.

console not showing updated values after setState [duplicate]

Ok, i'll try and make this quick because it SHOULD be an easy fix...
I've read a bunch of similar questions, and the answer seems to be quite obvious. Nothing I would ever have to look up in the first place! But... I am having an error that I cannot fathom how to fix or why its happening.
As follows:
class NightlifeTypes extends Component {
constructor(props) {
super(props);
this.state = {
barClubLounge: false,
seeTheTown: true,
eventsEntertainment: true,
familyFriendlyOnly: false
}
this.handleOnChange = this.handleOnChange.bind(this);
}
handleOnChange = (event) => {
if(event.target.className == "barClubLounge") {
this.setState({barClubLounge: event.target.checked});
console.log(event.target.checked)
console.log(this.state.barClubLounge)
}
}
render() {
return (
<input className="barClubLounge" type='checkbox' onChange={this.handleOnChange} checked={this.state.barClubLounge}/>
)
}
More code surrounds this but this is where my problem lies. Should work, right?
I've also tried this:
handleOnChange = (event) => {
if(event.target.className == "barClubLounge") {
this.setState({barClubLounge: !this.state.barClubLounge});
console.log(event.target.checked)
console.log(this.state.barClubLounge)
}
So I have those two console.log()'s, both should be the same. I'm literally setting the state to be the same as the event.target.checked in the line above it!
But it always returns the opposite of what it should.
Same goes for when I use !this.state.barClubLounge; If it starts false, on my first click it remains false, even though whether the checkbox is checked or not is based off of the state!!
It's a crazy paradox and I have no idea whats going on, please help!
Reason is setState is asynchronous, you can't expect the updated state value just after the setState, if you want to check the value use a callback method. Pass a method as callback that will be get executed after the setState complete its task.
Why setState is asynchronous ?
This is because setState alters the state and causes re rendering. This can be an expensive operation and making it synchronous might leave the browser unresponsive.
Thus the setState calls are asynchronous as well as batched for better UI experience and performance.
From Doc:
setState() does not immediately mutate this.state but creates a
pending state transition. Accessing this.state after calling this
method can potentially return the existing value. There is no
guarantee of synchronous operation of calls to setState and calls may
be batched for performance gains.
Using callback method with setState:
To check the updated state value just after the setState, use a callback method like this:
setState({ key: value }, () => {
console.log('updated state value', this.state.key)
})
Check this:
class NightlifeTypes extends React.Component {
constructor(props) {
super(props);
this.state = {
barClubLounge: false,
seeTheTown: true,
eventsEntertainment: true,
familyFriendlyOnly: false
}
}
handleOnChange = (event) => { // Arrow function binds `this`
let value = event.target.checked;
if(event.target.className == "barClubLounge") {
this.setState({ barClubLounge: value}, () => { //here
console.log(value);
console.log(this.state.barClubLounge);
//both will print same value
});
}
}
render() {
return (
<input className="barClubLounge" type='checkbox' onChange={this.handleOnChange} checked={this.state.barClubLounge}/>
)
}
}
ReactDOM.render(<NightlifeTypes/>, document.getElementById('app'))
<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>
<div id='app'/>
Since setState is a async function. That means after calling setState state variable does not immediately change. So if you want to perform other actions immediately after changing the state you should use callback method of setstate inside your setState update function.
handleOnChange = (event) => {
let inputState = event.target.checked;
if(event.target.className == "barClubLounge") {
this.setState({ barClubLounge: inputState}, () => { //here
console.log(this.state.barClubLounge);
//here you can call other functions which use this state
variable //
});
}
}
This is by-design due to performance considerations. setState in React is a function guaranteed to re-render Component, which is a costly CPU process. As such, its designers wanted to optimize by gathering multiple rendering actions into one, hence setState is asynchronous.

Resources