How to avoid unnecessary render?

reasonreact

#1

I’m a little confused on when exactly rerender happens with functional component (i.e. hooks).

    module GrandChildren = {
      [@react.component]
      let make = () => {
        <div> {React.string("Children")} </div>;
      };
    };

    module Children = {
      [@react.component]
      let make = (~active: bool) => {
        <div>
          {React.string("Children")}
          {active ? React.string("Active") : React.string("Inactive")}
          <GrandChildren />
        </div>;
      };
    };

    [@react.component]
    let make = () => {
      let (active, setActive) = React.useState(() => false);
      let (pactive, setPActive) = React.useState(() => false);

      <div>
        <button
          onClick={ev => {
            ReactEvent.Mouse.preventDefault(ev);

            setActive(v => !v);
          }}>
          {React.string("Toggle Child Active")}
        </button>
        <button
          onClick={ev => {
            ReactEvent.Mouse.preventDefault(ev);

            setPActive(v => !v);
          }}>
          {React.string("Toggle Parent Active")}
        </button>
        <div>
          {React.string("Parent")}
          {pactive ? React.string("Active") : React.string("Inactive")}
        </div>
        <Children active />
      </div>;
    };

If I profile the above code with react dev tool, I can observe the following behaviors.

  1. When parent component updates, it updates all the children components even when the parent component update does not affect the prop being passed to the children. In the example above, clicking on Toggle Parent Active should only set the state for pactive. active does not change yet the child still rerender.
  2. “Stateless” component (i.e. Grandchildren) still rerender everytime even when there is no props being pass.

Can someone explain why this is happening and what can I do to avoid unnecessary render.


#2

I think that the concept with stateless components may be that they are allowed to run at any point, and will be run many times, even if their props did not change. The trick is that even though they can run many extra times, they will execute very quickly unless some real work needs to actually happen. That real work consists of:

  1. DOM updates
  2. Reacting to state & prop changes

The DOM updates are automatically minimized by React’s reconciliation algorithm. You may already be familiar with that, but for anyone else, there is more info here: https://reactjs.org/docs/reconciliation.html

Regarding point number 2 – if you have some logic that depends on props and/or state, then it can be placed behind an effect hook to keep it from doing unnecessary work. That effect hook can be told to only run when there are changes to the state and props that it depends on. So even though your make/render function runs many times, your heavy-duty work will only run when it has to, and the effect hook makes sure of that, so long as you provide it the right dependencies array. More info on dependency arrays here: https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects


#3

The trick is that even though they can run many extra times, they will execute very quickly unless some real work needs to actually happen. That real work consists of:

  1. DOM updates
  2. Reacting to state & prop changes

Isn’t it when it rerenders the DOM also needs to be updated?

I am still confused because when the parent’s state changed, the state that actually changed is not related to the children’s prop but the children rerender anyway. In class based component, I can do the check in shouldComponentUpdate but I don’t see a way to do this in hook based component.

What I try instead is in the child component, I create a state that has the initial value from the prop. Then I have an effect to update the state only when the prop changes. However, this still rerenders the child even when the prop didn’t change.


#4

A component can rerender without doing any significant work or touching the DOM. Rerender just means that the render() / make() function of the component was called. In your example, it “rerenders” the child components, but it does not change the DOM.

React takes the output of render/make function and intelligently decides whether to touch the DOM or not. React is designed so that it can call render/make whenever it wants. As long as it returns the same JSX as the last time that it ran, then it won’t touch the DOM. Calling render() / make() is cheap, so it’s okay for react to “rerender” a child whenever the parent changes. Changing the DOM is expensive, so React only does that when it has to.


#5

rashkov is right that you don’t need to always optimize for minimum render count.

instead, if you think something is slow, run the React profiler to see how long the rendering takes and how often it is triggered.

example: if your render time is only 1ms but gets rendered 10 times, that’s 10ms. but if your one component renders once but takes ~50ms, this component is clearly slower.


#6

I don’t have much reason experience, but I have plenty on React.
Functional components will ran on every render unless you memoize them, but this is an optimization that has its drawbacks. So you will not be saving renders unless:

  • It’s absolutely essential
  • You memoize the component

Regards