Is this.setState({}) guaranteed to cause a re-render - reactjs

I inherited some code to maintain, with this line in it:
this.setState({}); // Force update
Is this guaranteed to cause a re-render?

setState merges the object passed as argument into the actual state in an immutable way. this.setState({}) will merge nothing to state but will actually return a new object, the shallow comparison performed by React will always assert to false and a re render will be triggered, unless explicitly cancelled with shouldComponentUpdate. So yes, in this case it is equivalent to forceUpdate and it comes with the same caveats.

You can actually test it easily:
import React, { Component } from 'react';
import { Button } from 'react-native';
class Test extends Component {
render() {
console.log('render');
return <Button onPress={() => this.setState({})} title='Test' />;
}
}
export default Test;
Every time the button is clicked, the console.log triggers.
An interesting point is that if you replace this.setState({}) by this.setState(), there is no re-render after a click.

According to the documentation it is and several other methods in order
static getDerivedStateFromProps()
shouldComponentUpdate()
render()
getSnapshotBeforeUpdate()
componentDidUpdate()
please see this link for detailed

It depends. If you want to render a component, react internally checks is DOM equals with previous(this occurs if props of component is not changed at all). If dom equals with previous version, react checks shouldComponentUpdate. forceUpdate is different than this.setState({}), which always render components.

Related

Avoid re-rendering a component which contains children props

How to avoid re-rendering a component which is having children props. React.memo seems to be working only on regular props
const FormGroup = ({
children
}) => {
return (
<div>
{children}
</div>
)
}
export default React.memo(FormGroup)
React.memo takes a second argument which lets you control how and which props are compared.
function FormGroup(props) {
/* render using props */
}
function areEqual(prevProps, nextProps) {
/*
return true if passing nextProps to render would return
the same result as passing prevProps to render,
otherwise return false
*/
return true;
}
export default React.memo(FormGroup, areEqual);
Note from docs:
This method only exists as a performance optimization. Do not rely on it to “prevent” a render, as this can lead to bugs.
React.memo and React.PureComponent both cannot optimize defaultly if children prop is passed. That is because on each re-render of parent a new instance of Children prop is created and hence the reference is lost.
This is explained in details in this github issue
Also as Dan Abramov mentioned in one of the comments within the the github link
You can choose to implement a custom shouldComponentUpdate for React.Component or an areEqual for React.memo. However doing this may often be an overhead and not necessarily be more beneficial than a render itself
Also render is cheap and fast and it okay to have a re-render for a simple component

When shouldComponentUpdate / render is called for child components of updated component

Having read a bunch of articles on the web about shouldComponentUpdate & render I want to double check if I get it correct when components are rerendered (render method is called) and when shouldComponentUpdate is called.
React docs (and dozens or articles say) that shouldComponentUpdate is called ONLY when new props are received or there is new state. But is seems that PureComponent does the same at first glance...
So to investigate it I wrote sample app:
parent component
import React, { Component } from 'react';
import './App.css';
import Hello from './Hello';
class App extends Component {
objWithName = { name: 'World' };
state = {
date: Date.now()
}
componentDidMount() {
setInterval(() => {
this.setState({ date: Date.now() });
}, 2000);
}
render() {
return (
<div className="App">
<p>Rendering timestamp {this.state.date}</p>
<Hello name={this.objWithName} />
</div>
);
}
}
export default App;
child component
import React from 'react';
export default class Hello extends React.Component {
shouldComponentUpdate() {
console.log('shouldComponentUpdate called');
return true;
}
render() {
console.log('render called');
return <p>Hello {this.props.name.name}</p>
}
}
So my first question is: is it completely true? I mean: In above code snippets, parent component in setInterval calls setState just to trigger its update, but this state is not used anywhere. And after doing that, child component is rerendered (render is called) & shouldComponentUpdate is called even though nothing has changed for him (it didn't receive any new props, nor state). I didn't find any explanation for this behaviour in React docs so I'm not sure how it works. What is more, if that child component didn't have any input props at (simply render static string) it would also get rerendered. Can sb explain it?
So my second question is: what does it mean new props/state is received by the component? Does it mean that object value is changed (for primitives simply new value, and for objects new reference)?
Third thing: assuming that a change in parent top most component (e.g. App.js) in the application happens (new prop or new state), does it mean that by default ALL react component that are currently rendered/mounted (even leafs that do not have any state, nor props that were changed) will rerender?
shouldComponentUpdate is just called from setState and forces render when returns true (default behaviour) - and updating DOM when different. This is by design to have a lifecycle/flow. When shouldComponentUpdate returns false then you can avoid unnecessary, usually costly render processing (and DOM update). PureComponent is just an optimized, ready to use component with shouldComponentUpdate defined to shallowly compare props (by comparing references).
React doesn't care if props/state are used, not using observables - you can use mobx for that.
New props means any change - it's shouldComponentUpdate responsibility to check swallowly or deep, depends what you need.
Yes, all children will be updated (forced to refresh node state/view in virtual DOM). It's fast/optimized process (operating on virtual tree) but they should use shouldComponentUpdate to limit theirs rerendering (and final DOM updates).

`componentWillReceiveProps` explanation

I recently wanted to upgrade my knowledge of React, so I started from the component lifecycle methods. The first thing that got me curious, is this componentWillReceiveProps. So, the docs are saying that it's fired when component is receiving new (not necessarily updated) props. Inside that method we can compare them and save into the state if needed.
My question is: Why do we need that method, if changes in props of that component (inside parent render) will trigger the re-render of this child component?
One common use case are state (this.state) updates that may be necessary in response to the updated props.
Since you should not try to update the component's state via this.setState() in the render function, this needs to happen in componentWillReceiveProps.
Additionally, if some prop is used as a parameter to some fetch function you should watch this prop in componentWillReceiveProps to re-fetch data using the new parameter.
Usually componentDidMount is used as a place where you trigger a method to fetch some data. But if your container, for example, UserData is not unmounted and you change userId prop, the container needs to fetch data of a user for corresponding userId.
class UserData extends Component {
componentDidMount() {
this.props.getUser(this.props.userId);
}
componentWillReceiveProps(nextProps) {
if (this.props.userId !== nextProps.userid) {
this.props.getUser(nextProps.userId);
}
}
render() {
if (this.props.loading) {
return <div>Loading...</div>
}
return <div>{this.user.firstName}</div>
}
}
It is not a full working example. Let's imagine that getUser dispatch Redux action and Redux assign to the component user, loading and getUser props.
It 'serves' as an opportunity to react to the incoming props to set the state of your application before render. If your call setState after render you will re-render infinitely and that's why you're not allowed to do that, so you can use componentWillReceiveProps instead.
But... you are beyond CORRECT in your confusion, so correct in fact that they are deprecating it and other Will-lifecycle hooks Discussion Deprecation.
There are other ways to accomplish what you want to do without most of those Will-lifecycle methods, one way being don't call setState after render, just use the new incoming props directly in render (or wherever) to create the stateful value you need and then just use the new value directly, you can then later set state to keep a reference for the next iteration ex: this.state.someState = someValue, this will accomplish everything and not re-render the component in an infinite loop.
Use this as an opportunity to react to a prop transition before render() is called by updating the state using this.setState(). The old props can be accessed via this.props. Calling this.setState() within this function will not trigger an additional render.
Look at this article
the componentWillReceiveProps will always receive as param "NxtProps", componentWillReceiveProps is called after render().
some people use this method use this to compare nxtProps and this.props to check, if something should happen before the component call render, and to some validations.
check the react's documentation to know more about react lifecycle!
hope this could help you!
changes in props of that component (inside parent render) will trigger the re-render of this child component
You are absolutely right. You only need to use this method if you need to react to those changes. For instance, you might have a piece of state in a child component that is calculated using multiple props.
Small Example:
class Test extends Component {
state = {
modified: "blank"
};
componentDidMount(){
this.setState({
modified: `${this.props.myProp} isModified`
});
}
componentWillReceiveProps(nextProps) {
this.setState({
modified: `${nextProps.myProp} isModified`
});
}
render() {
return <div className="displayed">{this.state.modified}</div>
}
}
In this example, componentDidMount sets the state using this.props. When this component receives new props, without componentWillReceiveProps, this.state.modified would never be updated again.
Of course, you could just do {this.props.myProp + "IsModified"} in the render method, but componentWillReceiveProps is useful when you need to update this.state on prop changes.

Redux mapStateToProps called multiple times

I have this very simple Component, which is connected to redux state and returns {fruit, vegetables}. Everything works fine, but let's say I have a graph inside the Component and I if receive only updated vegetable from API the graph is being recreated each time.
Here's my component:
const Products = ({ fruit, vegetable }) =>
<div className='Products'>
<div>{vegetable.map(....logic here)}</div>
<div>{Math.random()}</div> // this is to illustrate the component is rendering every time
<Graph>Here will be a chart or a graph with fruit</Graph> //but it gets re-rendered even though there is no new fruit
</div>
const mapStateToProps = (state) => {
return {
fruit: state.data.fruit,
vegetable: state.data.vegetable,
}
}
export default connect(mapStateToProps)(Products)
It seems to me that every-time, no matter which states is updated it re-renders the whole components.
Is there a way to prevent that?
When a React component gets rendered, the whole tree of components below it also gets rendered - at the exception of the components which shouldComponentUpdate hook returns false. So in your case, if the Products component gets rendered, it is normal that the Graph component also does.
You have two options here:
if your Products component does not use the fruit prop outside of the Graph component, you can connect directly your Graph component to the fruitstate, and use the pure option of the connect function to avoid re-renders when fruit does not change
you can define the shouldComponentUpdate hook in your Graph component to manually skip unnecessary renders, or use a helper library to do it for you, for example the pure helper of the recompose library
The first option is where optimizing react/redux apps / avoiding unnecessary renders generally starts: connect your components to the store at the lowest level where it makes sense. The second option is more of an escape hatch - but still often useful.
As you mention you use stateless components, you can use a higher-order component to benefit from the shouldComponentUpdate hook. To understand how this works, here's how a simple implementation of it could look like this:
function pure(BaseComponent, shouldUpdateFn) {
return class extends Component {
shouldComponentUpdate(nextProps) {
return shouldUpdateFn(this.props, nextProps);
}
render() {
return <BaseComponent { ...this.props } />;
}
}
}
This would give you a pure HOC that you could reuse over your app to avoid unnecessary renders: it works by wrapping your stateless component into a new component with the desired hook. You'd use it like so, for example:
export default pure(Graph, (props, nextProps) => props.fruit !== nextProps.fruit)
Still, i highly encourage you in having a look at recompose, which has more fine-grained implementations of this, and would avoid you to reinvent the wheel.
To prevent a component to rerender when receiving new props, you can implement shouldcomponentupdate() in Graph.
Use shouldComponentUpdate() to let React know if a component's output is not affected by the current change in state or props. The default behavior is to re-render on every state change, and in the vast majority of cases you should rely on the default behavior.
shouldComponentUpdate() is invoked before rendering when new props or state are being received. Defaults to true. This method is not called for the initial render or when forceUpdate() is used.
Returning false does not prevent child components from re-rendering when their state changes.
Currently, if shouldComponentUpdate() returns false, then componentWillUpdate(), render(), and componentDidUpdate() will not be invoked. Note that in the future React may treat shouldComponentUpdate() as a hint rather than a strict directive, and returning false may still result in a re-rendering of the component.
If you determine a specific component is slow after profiling, you may change it to inherit from React.PureComponent which implements shouldComponentUpdate() with a shallow prop and state comparison. If you are confident you want to write it by hand, you may compare this.props with nextProps and this.state with nextState and return false to tell React the update can be skipped.
To help you implementing shouldComponentUpdate(), you can use eitherReact shallow-compare() or a custom shallow compare function
Given your current code.
React will update the whole component when state is changed.
So Graph Component will get updated.
If you don't want Graph Component to get updated you can add shouldComponentUpdate in your Graph Component and introduce checks there for re-rendering like as follows
shouldComponentUpdate: function(nextProps, nextState) {
// You can access `this.props` and `this.state` here
// and check them against nextProps and nextState respectively.
// return boolean(false) if you don't want the component to re-render.
}

React component does not re-render, but render() gets called

This is my component. It renders the first time. Subsequently, even though render gets called, it does not change the DOM.
Render is getting called at the right times, because I have redux set up and my 'mapStateToProps' seems to work correctly, in that it detects a change in application state, and calls render().
The console log does log the fact that the string has changed.
You would probably like to see more code, but I'm hoping that I'm missing a fundamental concept here that someone can just point out. I'm not sure how to put my whole project up here. Thanks. Again, render DOES get called, so shouldn't it update the DOM?
import React from 'react';
import { Component } from 'react';
import { connect } from 'react-redux';
import {bindActionCreators} from 'redux';
class TinyMCETestResultElement extends Component {
render (){
console.log ("this.props.form0DataToDisplay " + this.props.form0DataToDisplay)// logs a changed string, as expected
return (
<div>
<div>results: {this.props.form0DataToDisplay}</div>
</div>
)
}
}
function mapStateToProps(state){
//whatever gets returned will show up as props inside of
return{
form0DataToDisplay: state.tinyMceTestData
}
}
export default connect( mapStateToProps, null)(TinyMCETestResultElement )
Math.random() in render block is the reason of such components behaviour.
React will rerender component only in case of it's state or props was changed. There is no updated props/state in your code sample, so component rendered only ones.
You can create variable in store for random value and update it with Math.random() into reducer. Then use it in your component as props and all wonna be ok.
It's probably because Redux's connected component is implementing shouldComponentUpdate for you, and it sees that your props don't change. As a result it won't update that component for you.
You can read about it here:
Redux Docs: http://redux.js.org/docs/basics/UsageWithReact.html
"...we suggest instead generating container components with the React Redux library’s connect() function, which provides many useful optimizations to prevent unnecessary re-renders. (One result of this is that you shouldn’t have to worry about the React performance suggestion of implementing shouldComponentUpdate yourself.)"
Twitter: https://twitter.com/dan_abramov/status/719723707124604928
A quick fix would probably be to implement shouldComponentUpdate to always return true, overriding Redux's implementation. Or better, having your props change everytime you get a new random number.
I should have mentioned that I was using a tiny mce component as the source of the change in state. If anyone comes across this question, there seemed to be an issue with loading that component, which could be fixed by re-rendering after initial load. I added this code to the tiny mce component to fix it. Timer may not be necessary.
componentDidMount(){
var that = this;
setTimeout(function(){
that.forceUpdate();
}, 1)
}

Resources