Why sometimes render is called immediately after setState and sometimes not - javascript

here is my codes:
class TestState extends Component{
constructor(props){
super(props);
this.state={
text:"123"
}
this._hello = this._hello.bind(this)
}
_hello(){
console.log("guangy will set state...");
let num = Math.floor(Math.random()*100);
this.setState({text: ""+num });
console.log("guangy after set state...");
}
componentWillUpdate(){
console.log("guangy componentWillUpdate")
}
render(){
console.log("guangy render.....")
return(<View>
<Text>{this.state.text}</Text>
<Button title="click" onPress={
()=>{
this._hello();
}
}/>
<Button title="click1" onPress={
()=>{
setTimeout(()=>{
this._hello();
}, 10);
}
}/>
</View>);
}
}
when i clicked the first button, the log is:
guangy will set state...
guangy after set state...
guangy componentWillUpdate
guangy render.....
and logs when clicked the second button:
guangy will set state...
guangy componentWillUpdate
guangy render.....
guangy after set state...
i think the render function should be called asynchronous, and actually in most situation it is, like when i clicked the first button, but when i clicked the second button, the render function seems to be called synchronous, because the "after set state" log is printed after the "render" log. why does this happen?

As per the 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.
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.
So one would think that setState is asynchronous. But if we look at some key words like:
React MAY delay it
setState() does not ALWAYS immediately update the component.
It MAY batch or defer the update until later.
So are we to think that setState is may or may not be an asynchronous operation?
Well, yep.
This is setState taken from the source code:
ReactComponent.prototype.setState = function(partialState, callback) {
invariant(
typeof partialState === 'object' ||
typeof partialState === 'function' ||
partialState == null,
'setState(...): takes an object of state variables to update or a ' +
'function which returns an object of state variables.'
);
this.updater.enqueueSetState(this, partialState);
if (callback) {
this.updater.enqueueCallback(this, callback, 'setState');
}
};
Looks like a normal synchronous function, well actually setState as it self isn't asynchronous but the effect of its execution may be.
I've done some testing myself and i realized that react will only update the state Asynchronously is when react has control of the entire flow, where react can't control of the flow, which means the the execution context is outside of it, then react will update the state immediately.
What can take react's control then?
Things like setTimeout, setInterval ajax request and other webApi's.
Even an event handler that attached outside react will trigger such behavior.
In fact here is a little snippet to satisfy our experiment.
I have a React component App which has a state with a myValue key and a method named onClick.
This method logs the current state, then call setstate then logs the state again.
App renders 3 elements:
The current value of myValue.
A button that we attach onClick through the react API.
A button that we attach onClick through the addEventListener API
(out side of react mind you).
When we click on the first button when react has control over the flow, the state is updated asynchronously.
When we click the second button, react will update the state immediately.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
myVal: 0
}
}
componentDidMount() {
const el = document.getElementById('btn');
el.addEventListener('click', this.onClick);
}
onClick = () => {
console.log('Pre setState', this.state.myVal);
this.setState({ myVal: this.state.myVal + 1 });
console.log('Post setState', this.state.myVal);
console.log('-------------------');
}
render() {
const { myVal } = this.state;
return (
<div>
<div>{myVal}</div>
<button onClick={this.onClick}>Managed by react</button>
<button id='btn'>Not Managed by react</button>
</div>);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<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="root"></div>

Related

What is the advantage of using componentDidUpdate over the setState callback?

Why is using componentDidUpdate more recommended over the setState callback function (optional second argument) in React components (if synchronous setState behavior is desired)?
Since setState is asynchronous, I was thinking about using the setState callback function (2nd argument) to ensure that code is executed after state has been updated, similar to then() for promises. Especially if I need a re-render in between subsequent setState calls.
However, the official React Docs say "The second parameter to setState() is an optional callback function that will be executed once setState is completed and the component is re-rendered. Generally we recommend using componentDidUpdate() for such logic instead."
And that's all they say about it there, so it seems a bit vague. I was wondering if there was a more specific reason it is recommended to not use it? If I could I would ask the React people themselves.
If I want multiple setState calls to be executed sequentially, the setState callback seems like a better choice over componentDidUpdate in terms of code organization - the callback code is defined right there with the setState call. If I use componentDidUpdate I have to check if the relevant state variable changed, and define the subsequent code there, which is less easy to track. Also, variables that were defined in the function containing the setState call would be out of scope unless I put them into state too.
The following example might show when it might be tricky to use componentDidUpdate:
private functionInComponent = () => {
let someVariableBeforeSetStateCall;
... // operations done on someVariableBeforeSetStateCall, etc.
this.setState(
{ firstVariable: firstValue, }, //firstVariable may or may not have been changed
() => {
let secondVariable = this.props.functionFromParentComponent();
secondVariable += someVariableBeforeSetStateCall;
this.setState({ secondVariable: secondValue });
}
);
}
vs
public componentDidUpdate(prevProps. prevState) {
if (prevState.firstVariableWasSet !== this.state.firstVariableWasSet) {
let secondVariable = this.props.functionFromParentComponent();
secondVariable += this.state.someVariableBeforeSetStateCall;
this.setState({
secondVariable: secondValue,
firstVariableWasSet: false,
});
}
}
private functionInComponent = () => {
let someVariableBeforeSetStateCall = this.state.someVariableBeforeSetStateCall;
... // operations done on someVariableBeforeSetStateCall, etc.
this.setState({
firstVariable: firstValue,
someVariableBeforeSetStateCall: someVariableBeforeSetStateCall,
firstVariableWasSet: true });
//firstVariable may or may not have been changed via input,
//now someVariableBeforeSetStateCall may or may not get updated at the same time
//as firstVariableWasSet or firstVariable due to async nature of setState
}
Also, apart from componentDidUpdate being generally recommended, in what cases would the setState callback be more appropriate to use?
Why is using componentDidUpdate more recommended over the setState callback function?
1. Consistent logic
When using the callback argument to setState(), you might have two separate calls to setState() in different places which both update the same state, and you'd have to remember to use the same callback in both places.
A common example is making a call to a third-party service whenever a piece of state changes:
private method1(value) {
this.setState({ value }, () => {
SomeAPI.gotNewValue(this.state.value);
});
}
private method2(newval) {
this.setState({ value }); // forgot callback?
}
This is probably a logic error, since presumably you'd want to call the service any time the value changes.
This is why componentDidUpdate() is recommended:
public componentDidUpdate(prevProps, prevState) {
if (this.state.value !== prevState.value) {
SomeAPI.gotNewValue(this.state.value);
}
}
private method1(value) {
this.setState({ value });
}
private method2(newval) {
this.setState({ value });
}
This way, the service is guaranteed to be called whenever the state updates.
Additionally, state could be updated from external code (e.g. Redux), and you won't have a chance to add a callback to those external updates.
2. Batched updates
The callback argument of setState() executes after the component is re-rendered. However, multiple calls to setState() are not guaranteed to cause multiple renders, due to batching.
Consider this component:
class Foo extends React.Component {
constructor(props) {
super(props);
this.state = { value: 0 };
}
componentDidUpdate(prevProps, prevState) {
console.log('componentDidUpdate: ' + this.state.value);
}
onClick = () => {
this.setState(
{ value: 7 },
() => console.log('onClick: ' + this.state.value));
this.setState(
{ value: 42 },
() => console.log('onClick: ' + this.state.value));
}
render() {
return <button onClick={this.onClick}>{this.state.value}</button>;
}
}
We have two setState() calls in the onClick() handler, each simply prints the new state value to the console.
You might expect onClick() to print the value 7 and then 42. But actually, it prints 42 twice! This is because the two setState() calls are batched together, and only cause one render to occur.
Also, we have a componentDidUpdate() which also prints the new value. Since we only have one render occurring, it is only executed once, and prints the value 42.
If you want consistency with batched updates, it's usually far easier to use componentDidMount().
2.1. When does batching occur?
It doesn't matter.
Batching is an optimization, and therefore you should never rely either on batching occurring or it not occurring. Future versions of React may perform more or less batching in different scenarios.
But, if you must know, in the current version of React (16.8.x), batching occurs in asynchronous user event handlers (e.g. onclick) and sometimes lifecycle methods if React has full control over the execution. All other contexts never use batching.
See this answer for more info: https://stackoverflow.com/a/48610973/640397
3. When is it better to use the setState callback?
When external code needs to wait for the state to be updated, you should use the setState callback instead of componentDidUpdate, and wrap it in a promise.
For example, suppose we have a Child component which looks like this:
interface IProps {
onClick: () => Promise<void>;
}
class Child extends React.Component<IProps> {
private async click() {
await this.props.onClick();
console.log('Parent notified of click');
}
render() {
return <button onClick={this.click}>click me</button>;
}
}
And we have a Parent component which must update some state when the child is clicked:
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = { clicked: false };
}
private setClicked = (): Promise<void> => {
return new Promise((resolve) => this.setState({ clicked: true }, resolve));
}
render() {
return <Child onClick={this.setClicked} />;
}
}
In setClicked, we must create a Promise to return to the child, and the only way to do that is by passing a callback to setState.
It's not possible to create this Promise in componentDidUpdate, but even if it were, it wouldn't work properly due to batching.
Misc.
Since setState is asynchronous, I was thinking about using the setState callback function (2nd argument) to ensure that code is executed after state has been updated, similar to .then() for promises.
The callback for setState() doesn't quite work the same way as promises do, so it might be best to separate your knowledge.
Especially if I need a re-render in between subsequent setState calls.
Why would you ever need to re-render a component in between setState() calls?
The only reason I can imagine is if the parent component depends on some info from the child's DOM element, such as its width or height, and the parent sets some props on the child based on those values.
In your example, you call this.props.functionFromParentComponent(), which returns a value, which you then use to compute some state.
Firstly, derived state should be avoided, since memoization is a much better option. But even so, why not just have the parent pass the value directly down as a prop? Then you can at least compute the state value in getDerivedStateFromProps().
//firstVariable may or may not have been changed,
//now someVariableBeforeSetStateCall may or may not get updated at the same time
//as firstVariableWasSet or firstVariable due to async nature of setState
These comments don't make much sense to me. The asynchronous nature of setState() doesn't imply anything about state not getting properly updated. The code should work as intended.

Using refs with conditional rendering

I have a problem with ref and conditional rendering.
I would like to focus an input tag when I click on a button tag.
Basically, I have this simplified code.
class App extends React.Component {
textInput
constructor(props) {
super(props)
this.state = {isEditing: false}
this.textInput = React.createRef()
}
onClick = () => {
this.setState({isEditing: !this.state.isEditing})
this.textInput.current.focus();
}
render () {
let edit = this.state.isEditing ?
(<input type="text" ref={this.textInput} />)
: ""
return (
<div>
<button onClick={this.onClick}>lorem </button>
{edit}
</div>
);
}
}
When I click on the button, the input tag is displayed but the ref textInput is still set to null. Thus I can't focus the input.
I found some workaround like:
set autoFocus property in the input tag
hide the input tag with css when isEditing == false
But actually it is a very basic pattern and I would like to know if there is a clean solution.
Thank you
TL;DR:
Change this:
this.setState({isEditing: !this.state.isEditing})
this.textInput.current.focus();
to this:
this.setState(previousState => ({isEditing: !previousState.isEditing}), () => {
this.textInput.current.focus();
});
Update: Functional Components / Hooks
It's been asked in the comments how to do this with useState and functional components. Rafał Guźniczak's answer explains it, but I wanted to provide a bit more explanation and a runnable example.
You still don't want to read state immediately after setting it, but instead of using a second argument callback to setState, you need to run some code after the state is updated and the component has re-rendered. How do we do that?
The answer is useEffect. The purpose of effects are to synchronize external "things" (for example: imperative DOM things like focus) with React state:
const { useEffect, useRef, useState } = React;
const { render } = ReactDOM;
function App(props) {
const [isEditing, setIsEditing] = useState(false);
const textInputRef = useRef(null);
const toggleEditing = () => setIsEditing(val => !val);
// whenever isEditing gets set to true, focus the textbox
useEffect(() => {
if (isEditing && textInputRef.current) {
textInputRef.current.focus();
}
}, [isEditing, textInputRef]);
return (
<div>
<button onClick={toggleEditing}>lorem </button>
{isEditing && <input type="text" ref={textInputRef} />}
</div>
);
}
render(
<App />,
document.getElementById('root')
);
<script src="https://unpkg.com/react#17/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom#17/umd/react-dom.development.js" crossorigin></script>
<div id="root"></div>
Details:
You're running into a common problem many people run into with React, which is the assumption that setting state is synchronous. It's not. When you call setState, you're requesting that React update the state. The actual state update happens later. This means that immediately after the setState call, the edit element hasn't been created or rendered yet, so the ref points to null.
From the docs:
setState() enqueues changes to the component state and tells React that this component and its children need to be re-rendered with the updated state. This is the primary method you use to update the user interface in response to event handlers and server responses.
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.
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.
Thank a lot for your answer #rossipedia. I was wondering if I can do it with hooks.
And apparently you can't pass second parameter to useState setter as in setState. But you can use useEffect like this (note second parameter in useEffect):
const [isEditing, setIsEditing] = React.useState(false);
React.useEffect(() => {
if (isEditing) {
textInput.current.focus();
}
}, [isEditing]);
const handleClick = () => setIsEditing(isEditing);
And it worked! ;)
Source: https://www.robinwieruch.de/react-usestate-callback/

React-redux : listening to state changes to trigger action

I have my redux state like this:
{
parks: [
{
_id:"ad1esdad",
fullName : "Some Name"
},
{
_id:"ad1es3s",
fullName : "Some Name2"
}
],
parkInfo: {
id : "search Id",
start_time : "Some Time",
end_time : "Some Time"
}
}
I have a parkSelector component from which a user selects parkId and start_time and end_time
import React, { Component } from 'react';
import { changeParkInfo } from '../../Actions';
class ParkSelector extends Component {
constructor(props) {
super(props);
this.handleApply = this.handleApply.bind(this);
this.rederOptions = this.rederOptions.bind(this);
this.state = {
startDate: moment().subtract(1, 'days'),
endDate: moment(),
parkId : this.props.parks[0]
};
}
handleApply(event) {
this.setState({
parkId : event.target.parkId.value
startDate: event.target.start_time.value,
endDate: event.target.end_time.value,
});
this.props.changeParkInfo(this.state.parkId,this.state.startDate,this.state.endDate);
}
rederOptions(){
return _.map(this.props.parks,(park,index)=>{
return(
<option value={park._id} key={park._id}>{park.profile.fullName}</option>
);
});
}
render() {
return (
<div className="row">
<div className="pb-4 col-sm-3">
<form onSubmit={this.handleApply}>
<select name="parkId" value={this.state.parkId} className="form-control input-sm">
{this.rederOptions()}
</select>
<input name="start_time" type="date" />
<input name="end_time" type="date" />
<button type="submit">Apply</button>
</form>
</div>
</div>
)
}
}
function mapStateToProps(state){
return {
parks : state.parks
};
}
export default connect(mapStateToProps,{ changeParkInfo })(ParkSelector);
I have another component 'stats' which needs to displays information related with parkInfo which will be loaded my api request.
import React, { Component } from 'react';
import StatsCard from '../../components/StatsCard';
import { getDashboardStats } from '../../Actions';
class Dashboard extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="animated fadeIn">
<div className="row">
<StatsCard text="Revenue Collected" value={9999} cardStyle="card-success" />
<StatsCard text="Total Checkins" value={39} cardStyle="card-info" />
<StatsCard text="Total Checkouts" value={29} cardStyle="card-danger" />
<StatsCard text="Passes Issued" value={119} cardStyle="card-warning" />
</div>
</div>
)
}
}
function mapStateToProps(state){
return {
parkInfo : state.parkInfo,
dashboardStats : state.dashboardStats
};
}
export default connect(mapStateToProps,{ getDashboardStats })(Dashboard);
I need to call getDashboardStats action (which makes api call and stores in results in dashboardStats of the redux state) whenever the redux state of parkInfo changes.
What is the best way to call this action, I have tried componentWillUpdate but it keeps on updating infinitely. What is best practice for this scenario ?
I had a similar problem but found a suitable approach. I believe the problem has to do with how the responsibilities of reducers and subscribers in a Redux app are often interpreted. My project did not use React, it was Redux-only, but the underlying problem was the same. To put emphasis on answering the underlying problem, I approach the subject from a general perspective without directly referring to your React-specific code.
Problem re-cap: In the app, multiple actions P,Q,R can cause a state change C. You want this state change C to trigger an asynchronous action X regardless of the action that originally caused the state change C. In other words, the async action X is coupled to the state change but intentionally decoupled from the wide range of actions (P,Q,R) that could cause the change C. Such situation does not happen in simple hello-todo examples but does happen in real-world applications.
Naïve answer 1: You cannot trigger another action, it is going to cause infinite loop.
Naïve answer 2: You cannot trigger another action, reducer must not trigger actions or cause any side effects.
Although both naïve answers are true, their base assumptions are wrong. The first wrongly assumes the action X is triggered synchronously and without any stopping condition. The second wrongly assumes the action X is triggered in a reducer.
Answer:
Trigger the action X in a subscriber (aka renderer) and in asynchronous manner. It might sound weird at first but it is not. Even the simplest Redux applications do it. They listen state changes and act based on the change. Let me explain.
Subscribers, in addition to rendering HTML elements, define how actions are triggered in a response to user behaviour. As well as dealing with user behaviour, they can define how actions are triggered in a response to any other change in the world. There is little difference between a user clicking a button after a five seconds and a setTimeout triggering an action after five seconds. As well as we let subscribers to bind an action to a click event or, say, found GPS location, we can let them bind an action to a timeout event. After init, these bindings are allowed to be modified at each state change just like how we can re-render a button or the whole page at a state change.
An action triggered by setTimeout will cause a loop-like structure. Timeout triggers an action, reducers update the state, redux calls subscribers, subscribers set a new timeout, timeout triggers action etc. But again, there is little difference to the loop-like structure caused by normal rendering and binding of events with user behaviour. They are both asynchronous and intended cyclic processes that allow the app to communicate with the world and behave as we like.
Therefore, detect your state change of interest in a subscriber and freely trigger the action if the change happened. Use of setTimeout can be recommended, even with the delay of zero, just to keep the event loop execution order clear to you.
A busy loop is of course a problem and must be avoided. If the action X itself causes such state change that will immediately trigger it again, then we have a busy loop and the app will stop to respond or become sluggish. Thus, make sure the triggered action does not cause such a state change.
If you like to implement a repeating refresh mechanism, for example to update a timer each second, it could be done in same manner. However, such simple repetition does not need to listen state changes. Therefore in those cases it is better to use redux-thunk or write an asynchronous action creator otherwise. That way the intention of the code becomes easier to understand.
If my understanding is correct, you need to make the API call to get the dashboardStats in your Dashboard component, whenever the parkInfo changes.
The correct life-cycle hook in this scenario would be the componentWillReceiveProps
componentWillReceiveProps(nextProps){
// this check makes sure that the getDashboardStats action is not getting called for other prop changes
if(this.props.parkInfo !== nextProps.parkInfo){
this.props.getDashboardStats()
}
}
Also note that, componentWillReceiveProps will not be called for the first time, so you may have to call the this.props.getDashboardStats() in componentDidMount too.
Goal: A change in parkInfo redux-state should prompt Dashboard to dispatch getDashboardInfo and re-render. (This behavior will also be similar in other components).
I use babel transform-class-properties, syntax is slightly different.
example:
// SomeLayout.js
import ParkSelector from 'containers/ParkSelector'
import Dashboard from 'containers/Dashboard'
const SomeLayout = () => {
return (
<div>
<ParkSelector />
<Dashboard />
</div>
)
}
export default SomeLayout
-
// Dashboard.js
// connect maps redux-state to *props* not state, so a new park selection
// will not trigger this component to re-render, so no infinite loop there
#connect((store) => ({ currentParkId: store.parkInfo.id }, //decorator syntax
{ getDashboardStats })
)
class Dashboard extends Component {
state = {
currentId: this.props.currentParkID,
parkInfoFoo: '',
parkInfoBar: ''
}
// using null for when no park has been selected, in which case nothing runs
// here.
componentWillReceiveProps(nextProps) {
// when Dashboard receives new id via props make API call
// assumes you are setting initial state of id to null in your reducer
if (nextProps.currentParkId !== null) {
getDashboardStats(`someurl/info/${nextProps.id}`).then((data) => {
// update state of Dashboard, triggering a re-render
this.setState({
currentId: nextProps.id
parkInfoFoo: data.foo,
parkInfoBar: data.bar
})
})
}
}
render() {
const { currentId, parkInfoFoo } = this.state
if (currentId !== null) {
return <span>{parkInfoFoo}</span>
}
return null
}
}
export default Dashboard
I think it should be this way:
Your component ParkSelector changes, you trigger the action via dispatch to changeParkInfo action.
This action does the AJAX and on it's success
1) you update the store state of parkInfo via another action.
2) you send getDashboardStats.
Now when point (2) is success, it will update the store state dashboardStats.
Next in your Dashboard you should not connect with parkInfo, reason: you are not using parkInfo in the dashboard.
Inside the dashboard component you should call action getDashboardStats in componentDidMount() for loading up the dashboardStats for first time when component loads up.
The idea in nutshell is when data changes action should call the facade of actions and make the state change.
What you trying is trigger an action which changes state that goes into component via props that triggers another action so on.. thus there is a infinite loop as you have written the component action in componentWillUpdate.
Hope this clarifies your question.

setState doesn't update the state immediately [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 view from setState() not triggering rerender when expected

Answer:
I'm a bonehead. This is way late but don't want to leave a thread unanswered and especially since my initial answer was wrong. I needed to reference the new props I was getting, not this.props. As usual the answer was in the documentation. I updated my own answer below to reflect this.
Edit 1:
My first fiddle did not fully show my issue so I've updated it to demonstrate better. For some reason when I call my setState() I believe the first pass through it is undefined even though its given a value, yet on subsequent passes it works as expected. It seems like my initial setState() call is not triggering a rerender but all others are.
A bit different to the usual "setState not updating view" question as setState() IS updating my view and with the correct value. Just not when I expect it to. Basically I am triggering a setState() event should rerender my child component with new props which I believe should trigger the childs components componentWillReceiveProps lifecyle event, which will then call setState() within the child component and update its view. The issue is while it does update the view, it seems to do it a cycle behind when expected. In my MVP I call setState() at 2 seconds yet it updates the view at 4 seconds. I haven't been able to determine which part is the bad logic though.
Here is my jsFiddle MVP. Thanks for any suggestions.
Code:
class TaskBody extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
componentWillReceiveProps() {
this.setState({
activeTask: this.props.activeTask
});
}
render() {
return <div>
< h4 > {
this.state.activeTask ? this.state.activeTask : 'Task Title'
} < /h4>
< /div > ;
}
}
class Body extends React.Component {
constructor(props) {
super(props);
this.state = {
activeTask: '',
counter: 1
};
this.updateActive = this.updateActive.bind(this);
}
updateActive(task) {
this.setState({
activeTask: task
});
}
componentDidMount(){
var self = this;
setInterval(function(){
if(self.state.counter === 4){
clearInterval(self.clickInterval);
return clearInterval(self.countInterval);
}
self.setState({ counter: self.state.counter + 1 });
}, 1000);
// imagine this was the click event, it should rerender the view
// instantaneously at 2 seconds because I called setState() right?
// which is supposed to trigger a re-render and then TaskBody should
// receive new props triggering it to setState() on its activeTask,
// which should update the view?
self.clickInterval = setInterval(function(){
self.setState({ activeTask: 'took double the time it should' });
}, 2000);
}
render() {
return <div>
< TaskBody activeTask = {
this.state.activeTask
}/>
<div>{this.state.counter}</div>
</div>;
}
}
ReactDOM.render(<Body />, document.querySelector('#body'));
The
self.setState({ activeTask: 'took double the time it should' });
is never actually called.
Here's why:
Your logic is located within the componentDidMount lifecycle method. If you read the documentation of componentDidMount, you'll see that it clearly states:
Invoked once, only on the client (not on the server), immediately
after the initial rendering occurs.
So, when componentDidMount gets called in your app, you're first checking the counter and calling a setState there. That alone will trigger a new render. This means that your second code block within componentDidMount is going to have no effect because while you set the method, it's never going to get called anywhere.
In my old answer, I was using a setTimeout() as a hack to get the results I wanted. Basically, if I wrapped my setState in a timeout it would set the state with the new props, but if I did not it would still reference the old props. Fortunately, this is already handled in react as componentWillReceiveProps already receives a newProps argument by default.
This:
componentWillReceiveProps(){
var self = this;
setTimeout(function(){
self.setState({activeTask: self.props.activeTask});
},0);
}
becomes
componentWillReceiveProps(newProps){
var self = this;
self.setState({activeTask: newProps.activeTask});
}

Categories

Resources