Reactjs - Does Render Get Called Any Time "Setstate" Is Called

ReactJS - Does render get called any time setState is called?

Does React re-render all components and sub-components every time setState is called?

By default - yes.

There is a method boolean shouldComponentUpdate(object nextProps, object nextState), each component has this method and it's responsible to determine "should component update (run render function)?" every time you change state or pass new props from parent component.

You can write your own implementation of shouldComponentUpdate method for your component, but default implementation always returns true - meaning always re-run render function.

Quote from official docs http://facebook.github.io/react/docs/component-specs.html#updating-shouldcomponentupdate

By default, shouldComponentUpdate always returns true to prevent
subtle bugs when the state is mutated in place, but if you are careful to
always treat the state as immutable and to read-only from props and state
in render() then you can override shouldComponentUpdate with an
implementation that compares the old props and state to their
replacements.

Next part of your question:

If so, why? I thought the idea was that React only rendered as little as needed - when the state changed.

There are two steps of what we may call "render":

  1. Virtual DOM renders: when render method is called it returns a new virtual dom structure of the component. As I mentioned before, this render method is called always when you call setState(), because shouldComponentUpdate always returns true by default. So, by default, there is no optimization here in React.

  2. Native DOM renders: React changes real DOM nodes in your browser only if they were changed in the Virtual DOM and as little as needed - this is that great React's feature which optimizes real DOM mutation and makes React fast.

React: Under what conditions does setState from the useState hook cause a re-render?

Via the documentation you linked to in your question:

If you update a State Hook to the same value as the current state, React will bail out without rendering the children or firing effects. (React uses the Object.is comparison algorithm.)

You can read there that React uses Object.is when comparing previous and new state values. If the return value of the comparison is true, then React does not use that setState invocation in considering whether or not to re-render. That is why setting your state value to true in the onClick handler doesn't cause a rerender.

That said, unconditionally calling a setState function at the top level of any component is always an error, because it initiates the reconciliation algorithm (infinitely). It seems to me that this is the core of your question, and if you want to learn about React Fiber (the implementation of React's core algorithm), then you can start here: https://github.com/acdlite/react-fiber-architecture

Here is another note from the React documentation (which needs to be updated for functional components):

You may call setState() immediately in componentDidUpdate() but note that it must be wrapped in a condition like in the example above, or you’ll cause an infinite loop.

↳ https://reactjs.org/docs/react-component.html#componentdidupdate

Explaining how class component lifecycle methods translate to functional components is out of scope for this question (you can find other questions and answers on Stack Overflow which address this); however, this directive applies to your case.

Here's a snippet showing that your component only renders once when the erroneous setState call is removed:

<script src="https://unpkg.com/react@17.0.2/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@17.0.2/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone@7.16.3/babel.min.js"></script>

<div id="root"></div>

<script type="text/babel" data-type="module" data-presets="react">

const {useRef, useState} = React;

function Example () {
const renderCountRef = useRef(0);
renderCountRef.current += 1;

const [bool, setBool] = useState(true);

return (
<div>
<div>Render count: {renderCountRef.current}</div>
<button onClick={() => setBool(true)}>{String(bool)}</button>
</div>
);
}

ReactDOM.render(<Example />, document.getElementById('root'));

</script>

Does render get called immediately after setState?

render function on the component will be executed immediately or after
function call is finished?

No one can take the guarantee of when that render function will get called, because setState is async, when we call setState means we ask react to update the ui with new state values (request to call the render method), but exactly at what time that will happen, we never know.

Have a look what react doc says about setState:

setState() enqueues changes to the component state and tells React
that this component and its children need to be re-rendered with the
updated state. This is the primary method you use to update the user
interface in response to event handlers and server responses.

Think of setState() as a request rather than an immediate command to update the component. For better perceived performance, React may
delay it, and then update several components in a single pass. React
does not guarantee
that the state changes are applied immediately.

Check this answer for more details about async behavior of setState: Why calling setState method doesn't mutate the state immediately?

Render called before setState updates state

A synchronous setState in componentWillMount doesn't trigger a re-render instead reflects the change in the initial render itself.The callback to setState however is called after the render method.

If you log the state squares inside the render method, it will log the correct result in the initial render itself.

P.S. componentWillMount is deprecated in the latest versions and its better to have your current logic in constructor instead of componentWillMount

Working demo

Why does React render component for the second time after setting the state to the same value?

The question is about why the component renders although the new state equals the previous state (shallow comparison)

// On second button click
const prevState = data

// State trigger
setFoo(data)

// Same state, it doesn't triggers a render
data === prevState

So, the component didn't trigger render due to state change.

But it happened due to another reason, as mentioned in docs "between the lines" under Hooks API Bailing out of a state update section:

Note that React may still need to render that specific component again before bailing out.

Unlike in class component, for function components, after setting the same state like in our case, sometimes, React will need another render to validate its equality. Its unfortunate edge case.

But it should not consider you as "performance issue" since it does not effect the React.Node tree, it won't continue in the reconciliation process if the state didn't change. It even won't make unnecessary hooks calls.

Another Simplified Example

Same goes here, there is another render for bail out, another log of "A".

Although there is a "bail out" render, notice that the useEffect does not run.

import React, { useEffect } from "react";
import ReactDOM from "react-dom";

const App = () => {
const [state, setState] = React.useState(0);

useEffect(() => {
console.log("B");
});

console.log("A");

return (
<>
<h1>{state}</h1>
<button onClick={() => setState(42)}>Click</button>
</>
);
};

ReactDOM.render(<App />, document.getElementById("root"));

Edit  React state trigger

If you wondering on logs order ("Why 'A' logged before 'B'?"), try deep diving another question: React useEffect in depth / use of useEffect?

React: component's render() is NOT invoked after this.setState() is called

Your handlePhoneNumberChange function mutates state directly. The React docs states: Never mutate this.state directly, as calling setState() afterwards may replace the mutation you made. Treat this.state as if it were immutable.

The simplest fix is to clone contactPhone, change it and pass it to state

handlePhoneNumberChange = index => (event) => {
const contactPhone = [...this.state.contactPhone]; // at this point contactPhone !== this.state.contactPhone
const phoneEntry = contactPhone[index];
phoneEntry.phoneNumber = event.target.value;
this.setState({ contactPhone });
};

Should work!


const cloneArray = [...someArray]; // spread syntax

is the same as

const cloneArray = someArray.slice(0);

Both produce an array that contains the same values as someArray but are not the same array.

react setState() not triggering re-render

Here you go: Codesandbox demo.

You should first make a shallow copy of the array you want to modify. Then set that array as the new state. Then it re-renders the component and you are able to filter like you want.

  const sort = (event) => {
console.log(event);
//shallow copying the state.
const newArr = [...sortedPlans];

//modifying the copy
newArr.sort((a, b) => {
return b[event] - a[event];
});
console.log(newArr); //results here are correctly sorted as per event

//setting the state to the copy triggers the re-render.
return setSortedPlans(newArr);
};

when does the `render()` method in react get called

when context gets changed render gets called

Would a component re-render if state inside of custom hook changes?

Yes it re-renders the component. The custom hooks helps to reuse stateful logic in different components, but in the end, when you use a custom hooks all states and effects inside of it are part of the component where you called the custom hook.
So yes, the change of state inside the custom hooks re-renders the component where you used your custom hook, even when this does not return anything.

Why does setState([...arr]) cause a rerender but setState(arr) does not?

React will only re-render a component if at least item in its state (or context) changes. If all previous state values are === to the new state, React will not call the functional component at all and simply keep using what was rendered the last time.

This is one of the reasons you should never mutate state in React - it can lead to unexpected or unpredictable behavior.

In your case, when you do setArr(arr), since the arr is the exact same array that's currently in state context, React skips re-rendering because the new state is === to the old state. Only by creating a new array which is not === to what is currently in state does a re-render occur.



Related Topics



Leave a reply



Submit