Rerender not working after states updated - reactjs

I have a parent component Rides which sends data to child component via props.
Here is the Child component:
class LazyLoader extends React.Component {
constructor(props){
super(props);
this.state = {
loaderState: this.props.loaderState
}
}
componentWillReceiveProps(nextProps){
console.log(`inside componentWillReceiveProps ${nextProps.loaderState}`);
if(this.props != nextProps) {
this.setState({
loaderState: nextProps.loaderState
}, () => {console.log(`Finished setState for ${nextProps.loaderState}`)});
}
}
render(){
console.log(`loaderState: ${this.state.loaderState}`);
return (
this.state.loaderState ? (
<View style={[styles.container]}>
<ActivityIndicator style={styles.spinner} animating={true} size={60} color='#fff'/>
</View>) : (<View/>)
)
}
}
And part of my parent component (here Rides) which send the updated data from its state to child's(here LazyLoader) prop:
export default class Rides extends React.Component {
constructor(props){
super(props);
this.handleRidePress = this.handleRidePress.bind(this);
this.navigateToRideDetails = this.navigateToRideDetails.bind(this);
this.state = {
flash: true
};
}
handleRidePress(rideId){
this.setState({
flash: true
}, () => {
console.log(`Begining handleRidePress for rideId: ${rideId}`);
if(doSomeTimeConsumingOperation){
// Time consuming operation goes here
}
this.navigateToRideDetails({rideId: rideId, pingData:pingData.slice(), rideData: rideDetails});
});
}
navigateToRideDetails(rideObj){
this.setState({
flash: false
}, () => {console.log(`navigateToRideDetails setState cb`); this.props.navigation.navigate('RideLog', rideObj);});
}
render(){
console.log(`flash: ${this.state.flash}`);
return(
<Gradient.FullGradient>
<Reusables.LazyLoader loaderState={this.state.flash}/>
<PRides rides={this.state.rideTiles} onDeletePress={this.deleteRide} navigation={this.props.navigation} onRideDetailPress={this.handleRidePress}/>
</Gradient.FullGradient>
)
}
}
When handleRidePress function is called it updates the flash state using setState() but child component doesn't get rerender. I tried using shouldComponentUpdate() in both component and returned true by default, but It doesn't work.

I think error is in the way you are comparing two objects here:
if(this.props != nextProps) {
....
}
This is not the correct way to check whether two objects are same or not, check the values:
if(this.props.loaderState != nextProps.loaderState) {
....
}
Check this answer How to determine equality for two JavaScript objects?
Check this snippet:
let a = {b: true};
let b = {b: true};
console.log(a == b); //false

I modified navigateToRideDetails to execute setState independently as react batches state updates that occur in event handlers and lifecycle methods. Thus, if we are updating state multiple times in a function handler, React will wait for event handling to finish before re-rendering. In my case, the flash value in state was getting reset after operation gets completed. Hence ActivityIndicator was not getting displayed.
Here is modified navigateToRideDetails function
navigateToRideDetails(rideObj){
setTimeout(() => {
this.setState({
flash: false
}, ()=>{
this.props.navigation.navigate('RideLog', rideObj);
});
}, 0);
}
This solves the issue :)

Related

Why are two network calls being made, when fetch in setState?

When I use fetch in setState the function makes two network requests, but I expect one request.
Why is this happening and how to prevent it?
import React from 'react';
class TestFetch extends React.Component {
constructor(props) {
super(props);
this.state = {};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(() => {
fetch('http://example.com/', {
mode: 'no-cors'
})
.then(data => {
console.log(data)
});
});
}
render() {
return (
<button onClick={this.handleClick}> Test </button>
)
}
}
export default TestFetch
Another version with setState in the fetch. Now I have one network call, but two values in my state after one click:
import React from 'react';
class TestFetch extends React.Component {
constructor(props) {
super(props);
this.state = {
'newItems': []
};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
fetch('http://example.com/', {
mode: 'no-cors'
})
.then(data => {
this.setState((state) => {
state.newItems.push("value")
})
console.log(this.state)
});
}
render() {
return (
<button onClick={this.handleClick}> Test </button>
)
}
}
export default TestFetch
Ok, basically it has this effect in this example as well:
import React from 'react';
class TestFetch extends React.Component {
constructor(props) {
super(props);
this.state = {
'newItems': []
};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(state => {
state.newItems.push("value")
})
console.log(this.state);
}
render() {
return (
<button onClick={this.handleClick}> Test </button>
)
}
}
export default TestFetch
Don't do api call in setState.. take state variable and store api response data in it and use state variable when ever it's required.
import React from 'react';
class TestFetch extends React.Component {
constructor(props) {
super(props);
this.state = {appData: null};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
fetch('http://example.com/', {
mode: 'no-cors'
})
.then(data => {
console.log(data)
this.setState(() => {appData: data});
});
}
render() {
return (
<button onClick={this.handleClick}> Test </button>
)
}
}
export default TestFetch
Why is this happening...
My guess would be you are rendering your app into a React.StrictMode component. See Detecting unintentional side-effects
Strict mode can’t automatically detect side effects for you, but it
can help you spot them by making them a little more deterministic.
This is done by intentionally double-invoking the following functions:
Class component constructor, render, and shouldComponentUpdate methods
Class component static getDerivedStateFromProps method
Function component bodies
State updater functions (the first argument to setState)
Functions passed to useState, useMemo, or useReducer
In other words, the setState is called twice by React to help you find unintentional side-effects, like the double fetching.
...and how to prevent it?
Just don't do side-effects in the setState callback function. You likely meant to do the fetch and in the Promise chain update state.
handleClick() {
fetch('http://example.com/', {
mode: 'no-cors'
})
.then(data => {
console.log(data);
this.setState( ......); // <-- update state from response data
});
}
Update
Another version with setState in the fetch. Now I have one network
call, but two values in my state after one click:
In your updated code you are mutating the state object. Array.prototype.push updates the array by adding the new element to the end of the array and returns the new length of the array.
Array.prototype.push
this.setState(state => {
state.newItems.push("value") // <-- mutates the state object
})
I believe you see 2 new items added for the same reason as above. When updating arrays in state you need to return a new array reference.
You can use Array.prototype.concat to add the new value and return a new array:
this.setState(prevState => {
newItems: prevState.newItems.concat("value"),
});
Another common pattern is to shallow copy the previous state array into a new array and append the new value:
this.setState(prevState => {
newItems: [...prevState.newItems, "value"],
});
Additionally, once you sort out your state updates, the console log of the state won't work because React state updates are asynchronously processed. Log the updated state from the componentDidUpdate lifecycle method.
componentDidUpdate(prevProps, prevState) {
if (prevState !== this.state) {
console.log(this.state);
}
}

State is not getting updated in componentWillMount

class ViewExchange extends React.Component{
state={list:[],refresh:false}
componentWillMount(props){
if(_.isEmpty(Cookies.get())){
this.props.history.push("/signup")
}
else{
console.log("is present")
let platform = Cookies.get('platform')
console.log(platform)
axios.post('http://localhost:3001/user/viewexchange',{platform})
.then(res=>{
console.log(res.data)
this.setState({list:res.data})})
console.log(this.state.list)
}
}
render(){
return (
<div>
<button onClick={()=>this.setState({refresh:true})}>refresh</button>
{console.log(this.state.refresh)}
</div>
);
}
}
export default withRouter(ViewExchange);
setState state update operations are async, so it will take a bit of time to update the state. But your log executes before it updates. Instead you can make use of passing function as a second param in the setState:
this.setState({list:res.data}, () => console.log(this.state.list))
another way is:
this.setState(state => {
state.list = res.data
}, () => console.log(this.state.list))
Whilst Jai's answer is correct, I also don't believe you are initialising your state correctly.
As you're using a class component you must initialise the state within the class's constructor.
constructor(props) {
super(props);
this.state = {
list: [],
refreshing: false,
};
}

React: how to use setState and render component when prop changes

This app is supposed to filter words by a specific input. I want to call a function with setState() when rendering a component and technically it's working but there is warning in the console.
Warning: Cannot update during an existing state transition (such as within render). Render methods should be a pure function of props and state.
I guess that this is because I'm calling the function in the render function which I shouldn't, but what should I do instead?
class UsersList extends React.Component {
constructor(props) {
super(props);
this.state = {
allUsers: ["Michał", "Ania", "Kasia", "Tomek", "Hubert", "Jan", "Martyna", "Rafał", "Bartłomiej"],
filteredUsers: [],
input: null
}
}
filter() {
if (this.state.input !== this.props.inputValue) {
const filtered = this.state.allUsers.filter(user => user.toLowerCase().includes(this.props.inputValue));
this.setState({
filteredUsers: filtered.map(user => <li key={user}>{user}</li>),
input: this.props.inputValue
})
}
return this.state.filteredUsers;
}
render() {
this.filter()
return (
<ul>
{this.state.filteredUsers}
</ul>
)
}
}
class App extends React.Component {
constructor() {
super();
this.state = {input: ""};
this.handleInput = this.handleInput.bind(this);
}
handleInput(e) {
this.setState({input: e.target.value})
}
render() {
return (
<div>
<input onChange={this.handleInput} type="search"/>
<UsersList inputValue={this.state.input} />
</div>
);
}
}
The issue here is caused by changes being made to your component's state during rendering.
You should avoid setting component state directly during a components render() function (this is happening when you call filter() during your component's render() function).
Instead, consider updating the state of your component only as needed (ie when the inputValue prop changes). The recommended way to update state when prop values change is via the getDerivedStateFromProps() component life cycle hook.
Here's an example of how you could make use of this hook for your component:
class UsersList extends React.Component {
constructor(props) {
super(props);
this.state = {
allUsers: ["Michał", "Ania", "Kasia", "Tomek",
"Hubert", "Jan", "Martyna", "Rafał",
"Bartłomiej"],
filteredUsers: [],
input: null
}
}
/* Add this life cycle hook, it replaces filter(). Props are updated/incoming
props, state is current state of component instance */
static getDerivedStateFromProps(props, state) {
// The condition for prop changes that trigger an update
if(state.input !== props.inputValue) {
const filtered = state.allUsers.filter(user => user.toLowerCase().includes(props.inputValue));
/* Return the new state object seeing props triggered an update */
return {
allUsers: state.allUsers
filteredUsers: filtered.map(user => <li key={user}>{user}</li>),
input: props.inputValue
}
}
/* No update needed */
return null;
}
render() {
return (<ul>{this.state.filteredUsers}</ul>)
}
}
Hope this helps
The error is coming up as it could create an endless loop inside the component. As render method is executed whenever the state is updated and your function this.filter is doing a state update. Now as the state updates, your render method triggers the function again.
Best way to do that would be in lifecycle methods or maintain the uses in the App and make UserList a dumb component by always passing the list of filtered users for it to display.

React child component can't get props.object

My parent component is like this:
export default class MobileCompo extends React.Component {
constructor(props) {
super(props);
this.state = {
data: null,
datasets: {}
};
this.get_data = this.get_data.bind(this);
}
componentWillMount() {
this.get_data();
}
async get_data() {
const ret = post_api_and_return_data();
const content={};
ret.result.gsm.forEach((val, index) => {
content[val.city].push()
});
this.setState({data: ret.result.gsm, datasets: content});
}
render() {
console.log(this.state)
// I can see the value of `datasets` object
return (
<div>
<TableElement dict={d} content={this.state.data} />
<BubbleGraph maindata={this.state.datasets} labels="something"/>
</div>
)
}
}
child component:
export default class BubbleGraph extends React.Component {
constructor(props) {
super(props);
this.state = {
finalData: {datasets: []}
};
console.log(this.props);
// here I can't get this.props.maindata,it's always null,but I can get labels.It's confusing me!
}
componentWillMount() {
sortDict(this.props.maindata).forEach((val, index) => {
let tmpModel = {
label: '',
data: null
};
this.state.finalData.datasets.push(tmpModel)
});
}
render() {
return (
<div>
<h2>{this.props.labels}</h2>
<Bubble data={this.state.finalData}/>
</div>
);
}
}
I tried many times,but still don't work,I thought the reason is about await/async,but TableElement works well,also BubbleGraph can get labels.
I also tried to give a constant to datasets but the child component still can't get it.And I used this:
this.setState({ datasets: a});
BubbleGraph works.So I can't set two states at async method?
It is weird,am I missing something?
Any help would be great appreciate!
Add componentWillReceiveProps inside child componenet, and check do you get data.
componentWillReceiveProps(newProps)
{
console.log(newProps.maindata)
}
If yes, the reason is constructor methos is called only one time. On next setState on parent component,componentWillReceiveProps () method of child component receives new props. This method is not called on initial render.
Few Changes in Child component:
*As per DOC, Never mutate state variable directly by this.state.a='' or this.state.a.push(), always use setState to update the state values.
*use componentwillrecieveprops it will get called on whenever any change happen to props values, so you can avoid the asyn also, whenever you do the changes in state of parent component all the child component will get the updates values.
Use this child component:
export default class BubbleGraph extends React.Component {
constructor(props) {
super(props);
this.state = {
finalData: {datasets: []}
};
}
componentWillReceiveProps(newData) {
let data = sortDict(newData.maindata).map((val, index) => {
return {
label: '',
data: null
};
});
let finalData = JSON.parse(JSON.stringify(this.state.finalData));
finalData.datasets = finalData.datasets.concat(data);
this.setState({finalData});
}
render() {
return (
<div>
<h2>{this.props.labels}</h2>
<Bubble data={this.state.finalData}/>
</div>
);
}
}

react immutability helper to render only changed subset of data

Please see the example here http://jsfiddle.net/8xzxkteu/1/
I'm trying to only render part of the data which is changed. In this example, state of component Main, data, is indexed by id and I am using react immutability helper to set only the changed one. But, if you click on the output, it renders all the children, as indicated by the counter. I though using immutability helper react can detect only part of the data changed hence only render it. I probably could use shouldComponentUpdate and compare object values for each child, but is there a better way doing this with immutability helper.
class Child extends React.Component {
constructor(props) {
super(props);
this.onClick = this.onClick.bind(this)
this.state = {
count: 0
};
}
componentWillReceiveProps(nextProps) {
var count = this.state.count + 1;
this.setState({ count: count });
}
onClick() {
this.props.onClick(this.props.name);
}
render() {
return <p onClick={this.onClick}>{this.props.name}: {this.props.value} {this.state.count}</p>;
}
}
class Main extends React.Component{
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this)
this.state = {
data: {
"a" : "a",
"b" : "b",
}
};
}
handleChange(id) {
this.setState({
data: React.addons.update(this.state.data, { [id]: { $set: 'x' } })
});
}
render() {
const keys = Object.keys(this.state.data);
const children = keys.map(k => {
return <Child name={k} value={this.state.data[k]} onClick={this.handleChange}/>
})
return <div>
{children}
</div>;
}
}
React.render(<Main />, document.getElementById('container'));
When you change state of component react call shouldComponentUpdate of this component and if it is return true react call render of this component.
After that react call componentWillReceiveProps, then shouldComponentUpdate, then render (if shouldComponentUpdate return true) of all child component.
By default, if there no shouldComponentUpdate method, it is considered that it has returned true. It does not matter whether you use immutable data or not - react does not know about it.
If you have immutable data you want avoid rerender, you should use shouldComponentUpdate. You can use pure-render-decorator, for example – it's check component state and props.
But if you change your state in componentWillReceiveProps you still get rerender because componentWillReceiveProps is called before shouldComponentUpdate.

Resources