React components automatically re-render whenever there is a change in their state or props. A simple update of the state, from anywhere in the code, causes all the User Interface (UI) elements to be re-rendered automatically.
The reason why this happens is an intentional feature of the React. StrictMode . It only happens in development mode and should help to find accidental side effects in the render phase.
But, is there an option to prevent re-rendering with functional components? The answer is yes! Use React. memo() to prevent re-rendering on React function components.
The error "Too many re-renders. React limits the number of renders to prevent an infinite loop" occurs for multiple reasons: Calling a function that sets the state in the render method of a component. Immediately invoking an event handler, instead of passing a function.
If you want a short snippet without any external dependencies I find this useful
componentDidUpdate(prevProps, prevState) {
Object.entries(this.props).forEach(([key, val]) =>
prevProps[key] !== val && console.log(`Prop '${key}' changed`)
);
if (this.state) {
Object.entries(this.state).forEach(([key, val]) =>
prevState[key] !== val && console.log(`State '${key}' changed`)
);
}
}
Here is a small hook I use to trace updates to function components
function useTraceUpdate(props) {
const prev = useRef(props);
useEffect(() => {
const changedProps = Object.entries(props).reduce((ps, [k, v]) => {
if (prev.current[k] !== v) {
ps[k] = [prev.current[k], v];
}
return ps;
}, {});
if (Object.keys(changedProps).length > 0) {
console.log('Changed props:', changedProps);
}
prev.current = props;
});
}
// Usage
function MyComponent(props) {
useTraceUpdate(props);
return <div>{props.children}</div>;
}
Here are some instances that a React component will re-render.
this.setState()
within the component. This will trigger the following component lifecycle methods shouldComponentUpdate
> componentWillUpdate
> render
> componentDidUpdate
props
. This will trigger componentWillReceiveProps
> shouldComponentUpdate
> componentWillUpdate
> render
> componentDidUpdate
(connect
method of react-redux
trigger this when there are applicable changes in the Redux store)this.forceUpdate
which is similar to this.setState
You can minimize your component's rerender by implementing a check inside your shouldComponentUpdate
and returning false
if it doesn't need to.
Another way is to use React.PureComponent
or stateless components. Pure and stateless components only re-render when there are changes to it's props.
You can check the reason for a component's (re)render with the React Devtools profiler tool. No changing of code necessary. See the react team's blog post Introducing the React Profiler.
First, go to settings cog > profiler, and select "Record why each component rendered"
@jpdelatorre's answer is great at highlighting general reasons why a React component might re-render.
I just wanted to dive a little deeper into one instance: when props change. Troubleshooting what is causing a React component to re-render is a common issue, and in my experience a lot of the times tracking down this issue involves determining which props are changing.
React components re-render whenever they receive new props. They can receive new props like:
<MyComponent prop1={currentPosition} prop2={myVariable} />
or if MyComponent
is connected to a redux store:
function mapStateToProps (state) {
return {
prop3: state.data.get('savedName'),
prop4: state.data.get('userCount')
}
}
Anytime the value of prop1
, prop2
, prop3
, or prop4
changes MyComponent
will re-render. With 4 props it is not too difficult to track down which props are changing by putting a console.log(this.props)
at that beginning of the render
block. However with more complicated components and more and more props this method is untenable.
Here is a useful approach (using lodash for convenience) to determine which prop changes are causing a component to re-render:
componentWillReceiveProps (nextProps) {
const changedProps = _.reduce(this.props, function (result, value, key) {
return _.isEqual(value, nextProps[key])
? result
: result.concat(key)
}, [])
console.log('changedProps: ', changedProps)
}
Adding this snippet to your component can help reveal the culprit causing questionable re-renders, and many times this helps shed light on unnecessary data being piped into components.
Strange nobody has given that answer but I find it very useful, especially since the props changes are almost always deeply nested.
Hooks fanboys:
import deep_diff from "deep-diff";
const withPropsChecker = WrappedComponent => {
return props => {
const prevProps = useRef(props);
useEffect(() => {
const diff = deep_diff.diff(prevProps.current, props);
if (diff) {
console.log(diff);
}
prevProps.current = props;
});
return <WrappedComponent {...props} />;
};
};
"Old"-school fanboys:
import deep_diff from "deep-diff";
componentDidUpdate(prevProps, prevState) {
const diff = deep_diff.diff(prevProps, this.props);
if (diff) {
console.log(diff);
}
}
P.S. I still prefer to use HOC(higher order component) because sometimes you have destructured your props at the top and Jacob's solution doesn't fit well
Disclaimer: No affiliation whatsoever with the package owner. Just clicking tens of times around to try to spot the difference in deeply nested objects is a pain in the.
Using hooks and functional components, not just prop change can cause a rerender. What I started to use is a rather manual log. It helped me a lot. You might find it useful too.
I copy this part in the component's file:
const keys = {};
const checkDep = (map, key, ref, extra) => {
if (keys[key] === undefined) {
keys[key] = {key: key};
return;
}
const stored = map.current.get(keys[key]);
if (stored === undefined) {
map.current.set(keys[key], ref);
} else if (ref !== stored) {
console.log(
'Ref ' + keys[key].key + ' changed',
extra ?? '',
JSON.stringify({stored}).substring(0, 45),
JSON.stringify({now: ref}).substring(0, 45),
);
map.current.set(keys[key], ref);
}
};
At the beginning of the method I keep a WeakMap reference:
const refs = useRef(new WeakMap());
Then after each "suspicious" call (props, hooks) I write:
const example = useExampleHook();
checkDep(refs, 'example ', example);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With