Reactjs: updating state immediately - reactjs

Complete Reactjs newbie here. I know setState() is asynchronous. I know if I do setState() then the states are queued and batched, so I will not see the state changed immediately. Fair enough.
I have also read the following link and other questions in SO:
https://reactjs.org/docs/react-component.html#setstate
I can use: callback methods in setState, componentDidUpdate() lifecycle method, concat if the state is an array etc. I get all these ways. My problem is a simple one, also I am banging my head on this issue for past 2 days so I am literally at my wit's end now.
I have this logic:
class ItemList extends React.Component {
constructor(props) {
super(props);
this.state = {showCompleted: false};
this.shouldShowFullList = false;
this.onShowCompleted = this.onShowCompleted.bind(this);
}
onShowCompleted(event) {
this.setState({showCompleted: event}, () => {this.shouldShowFullList = this.state.showCompleted;});
}
render() {
if (this.shouldShowFullList) {
return this.renderAllItemsIncludingCompleted();
} else {
return this.renderOnlyNotCompletedItems();
}
}
...
The logic is self-explanatory. My problem is even when I call this.shouldShowFullList in callback method of setState(), it still does not show an updated value. Value of this.shouldShowFullList is false when it should be true and vice versa. What is the best way to have the value of this.shouldShowFullList in lockstep with the this.state.showCompleted?
NOTE: onShowCompleted() is a callback method triggered from a child component. When a checkbox called "Show Completed" is checked, I should show a complete list of items, or else just the items which are not completed - something like ToDo list.

At onShowCompleted do
this.setState({ showCompleted: true })
or if you want toggle the value
this.setState({ showCompleted: !this.state.showCompleted })
Then in the render method you can do
if (this.state.showCompleted) {
return this.renderAllItemsIncludingCompleted();
} else {
return this.renderOnlyNotCompletedItems();
}
When you set a state using setState the method render is called with this.state updated. I think the callback (second argument) of this.setState is called after the render.
Because updating this.state make a new render it seems that react is pushing you to use this.state in the render method. In fact it's made for this usage. If you want to make a variable that have no purpose in the render you can use this.myVariable. Best pratice is to use only this.stateand this.props in the render (or function that depend of it).

Related

Trying to understand console.log behavior and state in react [duplicate]

This question already has answers here:
Why does calling react setState method not mutate the state immediately?
(9 answers)
Closed 7 months ago.
I'm trying to learn react and in doing so i need to do a lot of console.log(something). In the example below, could you please explain why the second console.log(this.state.foo);, the one within componentDidMount(), is not true? Thanks in advance.
Note: please don't suggest using functional components or hooks, the repo i will be working on is all class based components, so that's what i'm focusing on for now.
class App extends React.Component {
constructor (props){
super(props);
this.state = {
foo: false
}
console.log(this.state.foo);
}
componentDidMount() {
console.log('componentDidMount() lifecycle');
// Trigger update
this.setState({ foo: true });
// Why won't this one show in the console?
console.log(this.state.foo);
}
render() {
console.log('Render lifecycle')
return(
<h1>Hellos</h1>
)
}
}
ReactDOM.render( < App />, document.getElementById('container'));
That console.log is logging, but it's still logging false for the value this.state.foo. The reason for this is that setState is actually an asynchronous function (in other words, it takes time to execute completely), and you cannot expect the state update operation to have succeeded before you try to access the new value for state.
componentDidMount() {
console.log("componentDidMount() lifecycle");
// Updating state is asynchronous
this.setState({ foo: true });
// The line below is running, but the previous line updating state
// has not finished executing, which is why it's still logging false
console.log("component did mount", this.state.foo);
// => "component did mount", false
}
Here are the React docs on the topic.
As per docs: https://reactjs.org/docs/react-component.html#setstate
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.
Your
// Why won't this one show in the console?
console.log(this.state.foo);
Works and shows initial false value, that is actually expected. It will be updated on next shouldComponentUpdate call.

this.props got updated before shouldComponentUpdate run

I have my ClockBlock component where I lifted state keeped in object "timer":
class ClockBlock extends Component {
constructor(props){
super(props);
this.state = { timer: props.timer }; // timer from Redux store
}
render(){
return(
<div className="clockBlock">
<Circle timer={this.state.timer} updateTimer={this.updateTimer.bind(this)} />
<Clock timer={this.state.timer} updateTimer={this.updateTimer.bind(this)} />
<ClockTerms updateTimer={this.updateTimer.bind(this)} />
</div>
)
}
All the three nested component influence each other via updateTimer function. (except ClockTerms component - it works in one direction). The function is here:
updateTimer(newDuration){
const newState = Object.assign( {}, this.state );
newState.timer.duration = newDuration;
this.setState( newState );
}
So, the problem - when I change timer using ClockTerms component I see changes in the Clock component too (via props, apparently). In the same time in the Circle component in shouldComponentUpdate function i'm trying to see the difference between the old props and the new. Here is this function:
shouldComponentUpdate(nextProps, nextState){
console.log( this.state.timer );
console.log( nextState.timer );
console.log( this.props.timer );
console.log( nextProps.timer );
return true;
}
All the console.log() calls print the same data - new props. Why? And how can I get old props?
PS: i simplified my code above, removing irrelevant from my point of view calculations. Can give whole code, if it is important.
I also use Redux here, but it seems to me it isn't engaged here much.
Great thanks in advance!
UPDATE: I also get the same picture when place the same shouldComponentUpdate function in ClockBlock (parent) component;
You can functionally set state. Because setState is async, when multiple things call setState React uses the most recent properties passed to setState.
Instead you can update state by passing an updater function. This batches all the state updates. When Reacts lifecycle begins to update state it'll process all the pending updater functions in sequence.
This is from the docs describing what happens when setState uses an object.
Subsequent calls will override values from previous calls in the same
cycle, so the quantity will only be incremented once. If the next
state depends on the current state, we recommend using the updater
function form, instead:
this.setState((state) => {
return {quantity: state.quantity + 1};
});
https://reactjs.org/docs/react-component.html#setstate
I've solved a similar situation by comparing this.state to nextProps and updating state. I don't think it's a great solution, but didn't come up with anything else jet.
state = {
dayOff: this.props.myProperty
}
shouldComponentUpdate(nextProps, nextState) {
const shouldUpdate = this.state.myProperty !== nextProps.myProperty;
if (shouldUpdate) this.state.myProperty = nextProps.myProperty
return shouldUpdate;
}
The point is that updateTimer fires BEFORE shouldComponentUpdate runs. That's it. So, when you update your lifted state in the parent component from children components (via passed in props function), keep in mind that shouldComponentUpdate will get already changed state and props.

React Native - setState doesn't work in constructor

Here is my code:
I want to set functions results which I get them as promises to some state variables but as it seems after use this.setState in then() function it returns empty object!
I checked in then() block and it contains data as it's supposed to.
class ProfileScreen extends Component{
constructor(props){
super(props);
this.state = {
profile_data: {},
user_data: {}
};
this._getUserData().then(response => {
this.setState({user_data: response});
});
//Empty Results
console.log(this.state.user_data);
this._getProfileData(this.state.user_data.id).then(response => {
this.setState({profile_data: response});
})
}
}
First of all you should never set your state in constructor using setState as a call to setState leads to rerendering of the view heirarchy which is not yet built to be rerendered.
A better place for doing so is componentDidMount because thats a lifecycle callback which ensures that the DOM is already rendered once and now you can update your state to see the changes in the view.
Coming to your problem, the empty result is due to the fact that the setState is asynchronous in nature, thus the console.log would have run even before the setState has updated the state.
You should rely on the second parameter of the setState which is a callback that runs post the state update.
this.setState({
user_data: response
}, () => {
console.log(this.state.user_data);
})
Don't do this in your constructor. Fetch your user detail in componentDidMount and then set your state as per your need
In addition to what Suraj said, if _getUserData is asynchronous, perhaps doing an AJAX call, then _getProfileData must be inside the .then callback too since you depend on the user_data to fetch the profile_data.
Try this snack out: https://snack.expo.io/#transfusion/stackoverflow-53694220
It might be cleaner to just chain the promises together.

React Child Component Not Updating After Parent State Change

I'm attempting to make a nice ApiWrapper component to populate data in various child components. From everything I've read, this should work: https://jsfiddle.net/vinniejames/m1mesp6z/1/
class ApiWrapper extends React.Component {
constructor(props) {
super(props);
this.state = {
response: {
"title": 'nothing fetched yet'
}
};
}
componentDidMount() {
this._makeApiCall(this.props.endpoint);
}
_makeApiCall(endpoint) {
fetch(endpoint).then(function(response) {
this.setState({
response: response
});
}.bind(this))
}
render() {
return <Child data = {
this.state.response
}
/>;
}
}
class Child extends React.Component {
constructor(props) {
super(props);
this.state = {
data: props.data
};
}
render() {
console.log(this.state.data, 'new data');
return ( < span > {
this.state.data.title
} < /span>);
};
}
var element = < ApiWrapper endpoint = "https://jsonplaceholder.typicode.com/posts/1" / > ;
ReactDOM.render(
element,
document.getElementById('container')
);
But for some reason, it seems the child component is not updating when the parent state changes.
Am I missing something here?
There are two issues with your code.
Your child component's initial state is set from props.
this.state = {
data: props.data
};
Quoting from this SO Answer:
Passing the intial state to a component as a prop is an anti-pattern
because the getInitialState (in our case the constuctor) method is only called the first time the
component renders. Never more. Meaning that, if you re-render that
component passing a different value as a prop, the component
will not react accordingly, because the component will keep the state
from the first time it was rendered. It's very error prone.
So if you can't avoid such a situation the ideal solution is to use the method componentWillReceiveProps to listen for new props.
Adding the below code to your child component will solve your problem with Child component re-rendering.
componentWillReceiveProps(nextProps) {
this.setState({ data: nextProps.data });
}
The second issue is with the fetch.
_makeApiCall(endpoint) {
fetch(endpoint)
.then((response) => response.json()) // ----> you missed this part
.then((response) => this.setState({ response }));
}
And here is a working fiddle: https://jsfiddle.net/o8b04mLy/
If the above solution has still not solved your problem I'll suggest you see once how you're changing the state, if you're not returning a new object then sometimes react sees no difference in the new previous and the changed state, it's a good practice to always pass a new object when changing the state, seeing the new object react will definitely re-render all the components needing that have access to that changed state.
For example: -
Here I'll change one property of an array of objects in my state, look at how I spread all the data in a new object. Also, the code below might look a bit alien to you, it's a redux reducer function BUT don't worry it's just a method to change the state.
export const addItemToCart = (cartItems,cartItemToBeAdded) => {
return cartItems.map(item => {
if(item.id===existingItem.id){
++item.quantity;
}
// I can simply return item but instead I spread the item and return a new object
return {...item}
})
}
Just make sure you're changing the state with a new object, even if you make a minor change in the state just spread it in a new object and then return, this will trigger rendering in all the appropriate places.
Hope this helped. Let me know if I'm wrong somewhere :)
There are some things you need to change.
When fetch get the response, it is not a json.
I was looking for how can I get this json and I discovered this link.
By the other side, you need to think that constructor function is called only once.
So, you need to change the way that you retrieve the data in <Child> component.
Here, I left an example code: https://jsfiddle.net/emq1ztqj/
I hope that helps.
Accepted answer and componentWillReceiveProps
The componentWillReceiveProps call in accepted answer is deprecated and will be removed from React with version 17 React Docs: UNSAFE_componentWillReceiveProps()
Using derived state logic in React
As the React docs is pointing, using derived state (meaning: a component reflecting a change that is happened in its props) can make your components harder to think, and could be an anti-pattern. React Docs: You Probably Don't Need Derived State
Current solution: getDerivedStateFromProps
If you choose to use derived state, current solution is using getDerivedStateFromProps call as #DiogoSanto said.
getDerivedStateFromProps is invoked right before calling the render method, both on the initial mount and on subsequent updates. It should return an object to update the state, or null to update nothing. React Docs: static getDerivedStateFromProps()
How to use componentWillReceiveProps
This method can not access instance properties. All it does describing React how to compute new state from a given props. Whenever props are changed, React will call this method and will use the object returned by this method as the new state.
class Child extends React.Component {
constructor() {
super(props);
// nothing changed, assign the state for the
// first time to teach its initial shape.
// (it will work without this, but will throw
// a warning)
this.state = {
data: props.data
};
}
componentWillReceiveProps(props) {
// return the new state as object, do not call .setState()
return {
data: props.data
};
}
render() {
// nothing changed, will be called after
// componentWillReceiveProps returned the new state,
// each time props are updated.
return (
<span>{this.state.data.title}</span>
);
}
}
Caution
Re-rendering a component according to a change happened in parent component can be annoying for user because of losing the user input on that component.
Derived state logic can make components harder to understand, think on. Use wisely.

ComponentWillMount doesn't set the state React JS

Why this isn't working :
class Slider extends React.Component {
constructor(props){
super(props);
this.state = {
top : 0,
responsiveImg : ""
};
}
componentWillMount(){
const bodyWidth = window.innerWidth;
const responsiveSliderImg = "responsiveSliderImg";
if((bodyWidth >= 415) && (bodyWidth < 473)){
this.setState({responsiveImg: responsiveSliderImg});
console.log("The state is : " + this.state.responsiveImg);
}
}
}
I'm trying to add a class name to the state in componentWillMount function, but it doesn't work. In console log I get The state is : , thus the state is empty.
Any idea?
State doesn't update synchronously. From the 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.
Try checking the state in your render method.
It's also good to become aware of which of the lifecycle methods you can call setState in without a re-render occurring. Can be helpful in some cases. I use reactcheatsheet as a quick reference. Click the "lifecycle" filter to see a very helpful table reference.
As a matter of interest. I have developed a utility library to help with responsive use cases similar to yours. It provides your component with the size of it's container allow you to do responsive render logic. It could be helpful to you? Check it out here.
setState is not immediately update state., if you need get new state you can pass callback as second argument to setState
this.setState({responsiveImg: responsiveSliderImg}, function () {
// get new state here
});
Example

Resources