Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Trace why a React component is re-rendering

Tags:

reactjs

redux

People also ask

Why is my component re rendering React?

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.

Why is my component re rendering twice?

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.

How do I stop React component from re rendering?

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.

What causes too many re renders?

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.

  • Parent component rerender
  • Calling this.setState() within the component. This will trigger the following component lifecycle methods shouldComponentUpdate > componentWillUpdate > render > componentDidUpdate
  • Changes in component's 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)
  • calling 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"

React Dev Tools > Settings

Screenshot of React Devtools profiler


@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);