What causes my component to be rendered again in React?

Asked

Viewed 196 times

5

Very commonly I see talk that a component of React will be re-rendered because of a prop, state, value of context altered, etc..

  • What are the factors that actually influence the re-rendering of a component?
  • In the functional components, there is some relationship with useEffect, useMemo or useCallback?

Note that I’m not specifically interested in first rendering, but rather the factors that make a second (or third, fourth, etc.) rendering occur.

I do not know if, in this matter, there is a distinction between the "functional components" and the (old) "class components". If there is, I would like it to be emphasized in the functional components, but a brief mention of this in relation to the class components would also be nice.

  • is very broad, mixing Oks with Komponent class that are different the way of doing, but, the behavior is the same.

  • 1

    @novic, at the end of the question I preferred to put the focus on the functional components themselves (which give the relationship with the Hooks), so I do not think that actually occurs this "mixture"...

  • The difference is how to do, the styles, that same programming styles, but, the end result is the same, I understood that you might want to point out differences (há distinção) but in fact reinforcing the generated code is the same. Maybe the long awaited new version will be different

3 answers

4


Summary

The answer got a little long. In short, React has an algorithm that compares two tree elements in the DOM to know whether to update them or not. This algorithm will update the DOM tree when:

  • The root node of the tree is of different type (e. g. <div> <a>Oi</a> </div> and <button> <a>Oi</a> </button>);
  • Some attribute has been modified (e. g. <div className="a" /> and <div className="b' />);
  • Some component property has been modified (e. g. <Timer time={10} /> and <Timer time={15} />);
  • Some state of the component has been modified;
  • The comparison of child nodes finds differences. This comparison is a recursion on the two trees at the same time, comparing node to node.

And, the relationship with the Hooks mentioned is simply that they are executed in every rendering, but make use of an array of dependencies to define whether the callback passed to these Hooks must be run again or not.


How the re-rendering works?

This topic and every explanation of the algorithm of diff is based on documentation Reconciliation.

Whenever the render() is executed in a class component, or when a functional component is executed, an element tree is returned. When the state or properties are updated, a new tree is returned. So, React needs to differentiate the two trees to know if it needs to render something.

The re-rendering does not disassemble and reassemble the components. It only updates the differences found according to the rules defined by the diff, explained below.

Algorithm of diff

To identify the differences in a computationally inexpensive way, React created its own algorithm for diff, implementing an O(n) heuristic algorithm based on two premises:

  1. Two elements of different types will produce different trees;
  2. The developer can suggest which child elements can be stable in different renderings with a property key.

The behavior of diff varies according to the type of the root elements to be compared.

Elements of different types

When the root node of a tree is modified, React will disassemble the entire tree and mount a new one from scratch. For example:

<div>
  <Counter />
</div>

<span>
  <Counter />
</span>

The above code will make the Counter be reassembled.

DOM elements of the same type

When updating DOM elements of the same type, React observes the attributes, updating only the modified attributes. For example:

<div className="before" title="stuff" />

<div className="after" title="stuff" />

In this case, React will only modify the className node. In the case of the attribute style, React can identify which property has been modified.

Components of the same type

When a component updates, the instance is the same, then the state is maintained between renderings. React updates component instance properties to match the new element.

After this update of the properties, the algorithm of diff is executed by comparing the previous result to the current one.

Note: As the documentation of Reconciliation barely cites the State, I want to quote another documentation here, the State and Lifecycle. This one says, thanks to the call from setState, React knows there’s been a change of status, and then executes the render() to check what should be modified on the screen. That’s why you can’t modify the state directly, without calling the function setState().

Although the example given is for class components, this information can be taken to the functional components.

Recursion in the children

By default, when there is recursion on the children of a DOM node, React only iterates on both child lists at the same time and generates a mutation whenever there is a difference. For example:

<ul>
  <li>primeiro</li>
  <li>segundo</li>
</ul>

<ul>
  <li>primeiro</li>
  <li>segundo</li>
  <li>terceiro</li>
</ul>

React will understand that the <li>primeiro</li> and <li>segundo</li> are equal, and will add the node <li>terceiro</li>. Because of this, inserting an element at the beginning or middle of the list will cause more than one element to be rendered unnecessarily.

Keys (key)

To solve the above problem, React accepts the attribute key in any element. When children have a key defined, it is used to check whether they represent the same node or not, and then perform the re-rendering when necessary.

That’s why we use the index as key is not always a valid option. You would end up in the same situation of not using key none.

For more information about the property key, read What it’s for and how to set the prop "key" in React?

Attention factors

According to the algorithm explained above, there are two points of attention:

  1. The algorithm will not attempt to combine sub-trees of different types of components. If you switch between two types of components with very similar output, it is convenient to make them of the same type. In practice, this is not considered a problem.

  2. The keys (key) must be stable, predictable and unique. Unstable keys (such as those produced by Math.random() or Date.now()) will cause many instances of components and DOM nodes to be unnecessarily recreated, which can cause degradation of performance and loss of status in child components.


The above mentioned Hooks

useEffect (or componentDidUpdate)

In accordance with the documentation says, by default, the effects run after each completed rendering, but you can choose to run them only when certain values are changed (based on the dependency array). There is a question that addresses this subject: What is and why should I provide a "dependency array" for React’s Hooks?

useMemo

The useMemo is executed during rendering and returns a value memoized. It also receives an array of dependencies, causing the value to be recalculated only when one of those dependencies is modified. It is useful for calculations "expensive".

useCallback

Similar to the useMemo, the useCallback returns a callback memoized (and not a value). It is useful for times that depend on comparison by reference to avoid a re-rendering.

I wrote a more in-depth response on the useMemo and useCallback in What is the difference between React’s useMemo and useCallback?

What they have in common?

All these Hooks are executed at each render. The point is that they accept an array of dependencies to define whether the callback function passed to them should actually be executed or not.

For example, an empty array as in useCallback(() => {}, []) means it will only be executed once, since no value will be updated in the next renderings (there is no value in the array).

Already in the case useMemo(() => fatorial(count), [count]), means that whenever there is a rendering and count have its value modified, the () => fatorial(count) will be executed.

Then, the dependency array does not cause a re-rendering, but it is used after re-rendering to know if the callback will be executed or not.

0

Short answer:

Changes of Constants

Long answer:

useState returns an array with its first position being the constant and the second position being a function to set new values to that constant.

const [user , setUser] = useState(null);

Throughout the code when using the setUser will cause this constant to be changed, but for this to occur it is necessary to render the component again so that in fact the constant can receive this new value.

useEffect is only fired AFTER the component has been mounted on screen, that is everything inside it is ignored in the first render.

if Voce has:

const [user, setUser] = useState(null);

useEffect(()=>{
  setUser({name:'stackOverflow'});
},[]);

return (
  {!user && <h1>Ainda sem usuario</h1>}
  {user && <h1>{user.name}</h1>}
)

The first render the user is null. After rendering the useEffect is triggered in which you will make a change to a constant. As we have seen above the change of a constant will trigger the rerender.

If the useEffect has no state change for obviousness it will not fire the second render. But in 99.9% of cases (if not 100%) useEffect is used to run something that will change the UI.

0

I am learning and practicing React and your doubts fried many of my neurons today, sincerely thank you, after a good research I found a very good content that can help you I believe(helped me) guide to React Rendering behavior.

  • useMemo and useCallback are triggered in render Phase and are basically for optimization, but should not contain vital parts.
  • useEffect is triggered in the 'commit Phase' which happens after render Phase' because it is usually reserved to contain code that can cause inconsistency in the UI.

diagram - render Phase & commit Phase

Browser other questions tagged

You are not signed in. Login or sign up in order to post.