admin管理员组

文章数量:1357291

If we set the state with the same value ponent won't re-render, but it's not applicable when I set the state in the function body.

For example, if I set the same state on the button click and the button clicked, the ponent does not re-rendering on the button click

function Test1() {
  const [name, setName] = useState("Shiva");
  const onButtonClick = () => {
    console.log("Clicked");
    setName("Shiva");
  };
  console.log("Redering");
  return (
    <div>
      <span>My name is {name}</span>
      <button onClick={onButtonClick}>Click Me</button>
    </div>
  );
}

But, when I set the same state before the return statement React goes infinite renderings

function Test2() {
  const [name, setName] = useState("Shiva");
  // ... e stuff
  setName("Shiva");
  console.log("Rendering");
  return (
    <div>
      <span>My name is {name}</span>
    </div>
  );
}

What actually happening internally?

If we set the state with the same value ponent won't re-render, but it's not applicable when I set the state in the function body.

For example, if I set the same state on the button click and the button clicked, the ponent does not re-rendering on the button click

function Test1() {
  const [name, setName] = useState("Shiva");
  const onButtonClick = () => {
    console.log("Clicked");
    setName("Shiva");
  };
  console.log("Redering");
  return (
    <div>
      <span>My name is {name}</span>
      <button onClick={onButtonClick}>Click Me</button>
    </div>
  );
}

But, when I set the same state before the return statement React goes infinite renderings

function Test2() {
  const [name, setName] = useState("Shiva");
  // ... e stuff
  setName("Shiva");
  console.log("Rendering");
  return (
    <div>
      <span>My name is {name}</span>
    </div>
  );
}

What actually happening internally?

Share Improve this question edited Oct 26, 2022 at 23:47 Shiva asked Oct 26, 2022 at 23:27 ShivaShiva 5822 gold badges4 silver badges18 bronze badges 10
  • No, I'm asking why the ponent is not re-rendered when I set the same state on the button click and why it's re-render if set before the return statement. – Shiva Commented Oct 26, 2022 at 23:34
  • 2 @Shiva it does, when set state is called in the main body of the function it gets executed during the render. When the set state is defined inside the click function, that click function is just defined during the render, it's not actually executed until you click the button. So set state is not called until button is clicked, as opposed to during the render – Jayce444 Commented Oct 26, 2022 at 23:39
  • hi @Jayce444, even I click on the button in the above example < Test1 /> ponent is not getting re-rendered. – Shiva Commented Oct 26, 2022 at 23:43
  • 4 This is a very interesting question. I'd take a guess that it's to do with React's state tracking and where you're calling setName(). When you call it before the render (aka the return value), React probably hasn't had a chance to track the value so it's always triggering a re-render – Phil Commented Oct 27, 2022 at 0:29
  • 1 Does this answer your question? Updating state to the same state directly in the ponent body – Nick Parsons Commented Oct 29, 2022 at 4:33
 |  Show 5 more ments

2 Answers 2

Reset to default 8 +50

React uses different approaches to schedule updates based on the place where you called your setState.

For example, your setState inside the event handler will use

export function enqueueConcurrentHookUpdate<S, A>(
  fiber: Fiber,
  queue: HookQueue<S, A>,
  update: HookUpdate<S, A>,
  lane: Lane,
)

While your setState in ponent top level use

function enqueueRenderPhaseUpdate<S, A>(
  queue: UpdateQueue<S, A>,
  update: Update<S, A>,
)

When you call setState, React will internally call a function,

function dispatchSetState<S, A>(
  fiber: Fiber,
  queue: UpdateQueue<S, A>,
  action: A,
)

If you check the declaration of this function, you will find a top level conditional check,

  if (isRenderPhaseUpdate(fiber)) {
    enqueueRenderPhaseUpdate(queue, update);
  } else {}

Your setState inside the event handler will use the else block while the setState in render phase ( top level function body ) will use if block.


Additional

so how does React decided whether its in render phase or not ? If you check the code of the isRenderPhaseUpdate you can see,

function isRenderPhaseUpdate(fiber: Fiber) {
  const alternate = fiber.alternate;
  return (
    fiber === currentlyRenderingFiber ||
    (alternate !== null && alternate === currentlyRenderingFiber)
  );
}

Now you might heard about virtual DOM, actually its a linked list. Each object of the linked list is known as fiber nodes. these fiber nodes are nothing more than plain javascript objects. Each of these fiber nodes has a field called, alternate.

There can be 2 separate fiber trees ( virtual doms ). One of this fiber tree is know as current tree which is the one mitted to the DOM. The other fiber tree is known as the work-in-tree. This is the one react builds newly when states updates happens yet not mitted to the DOM.

So for a given Component there can be maximum two fiber nodes ( one from current tree & another one from work-in-tree ). These two fiber nodes are connected using alternate field.

currentlyRenderingFiber is a global variable which keep track the currently rendering fiber node by React.

Now you should able to understand the body of above isRenderPhaseUpdate function.

Additional Explanation Ends


CASE 01 - Event Handler

When you trigger the setState from event handler react will use else block of the above function.

If you check the body of the else block you will find out following code snippet,

const currentState: S = (queue.lastRenderedState: any);
  const eagerState = lastRenderedReducer(currentState, action);
  update.hasEagerState = true;
  update.eagerState = eagerState;
  if (is(eagerState, currentState)) { // <-- checks if the previous value is equal to current one.
     enqueueConcurrentHookUpdateAndEagerlyBailout(fiber, queue, update);
     return; // <-- return early without call `enqueueConcurrentHookUpdate`
  }
  // never reaches here if the `eagerState` & `currentState` are same
  const root = enqueueConcurrentHookUpdate(fiber, queue, update, lane);
  if (root !== null) {
    const eventTime = requestEventTime();
    scheduleUpdateOnFiber(root, fiber, lane, eventTime);
    entangleTransitionUpdate(root, queue, lane);
  }

As you can see when is(eagerState, currentState) true ( Which is true in your case as both eagerState & currentState holds the value "Shiva" ) React will exit from the function early without calling enqueueConcurrentHookUpdate. This is why React won't re render for the same value again.

CASE 02 - Top level of the ponent

When you call setState from the top level of your ponent, it will run when React is traversing the ponent tree and calling your ponents ( due the execution of ponent body by React )

Notice that React is the one who call your ponents. You are just defining them. React call your ponents and get the output when traversing aka when rendering & building the new fiber tree ( aka work-in-progress tree )

Now if you check the body of

function enqueueRenderPhaseUpdate<S, A>(
  queue: UpdateQueue<S, A>,
  update: Update<S, A>,
) {
  // This is a render phase update. Stash it in a lazily-created map of
  // queue -> linked list of updates. After this render pass, we'll restart
  // and apply the stashed updates on top of the work-in-progress hook.
  didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true;
  const pending = queue.pending;
  if (pending === null) {
    // This is the first update. Create a circular list.
    update.next = update;
  } else {
    update.next = pending.next;
    pending.next = update;
  }
  queue.pending = update;
}

Notice the code ment of the above function. It creates a circular linked list of schedule updates causes from the setState in your ponent top level body.

According to the code ment, these stashed updates in the circular linked list will apply to the hook in your latest fiber node

Now notice the line in function enqueueRenderPhaseUpdate,

didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true;

didScheduleRenderPhaseUpdateDuringThisPass is a global variable. This variable is use for a while loop,

do {
    didScheduleRenderPhaseUpdateDuringThisPass = false;

    if (numberOfReRenders >= RE_RENDER_LIMIT) {
      throw new Error(
        'Too many re-renders. React limits the number of renders to prevent ' +
          'an infinite loop.',
      );
    }
    numberOfReRenders += 1;

    // some other code

    children = Component(props, secondArg);
  } while (didScheduleRenderPhaseUpdateDuringThisPass);

Each time your ponent body get executed ( Component(props, secondArg); in while loop), you are triggering enqueueRenderPhaseUpdate due to the setState in your ponent body which sets didScheduleRenderPhaseUpdateDuringThisPass to true which triggers the while loop again which calls the Component again.

Once the loop executed 25 times, React will throw an error.

You can find these function in,

  1. \react\packages\react-reconciler\src\ReactFiberHooks.new.js ( line 2538 )
  2. \react\packages\react-reconciler\src\ReactFiberHooks.old.js

React uses virtual DOM to update the changes into view. Virtual DOM has the rendered state when the ponent is rendered into view, that is after the execution of return statement in a functional ponent.

Component Test1:

function Test1() {
    const [name, setName] = useState("Shiva");
    const onButtonClick = () => {
        // State updates on button click happens after ponent gets rendered in view
        setName("Shiva");
    };
    return (
        <div>
            <span>My name is {name}</span>
            <button onClick={onButtonClick}>Click Me</button>
        </div>
    );
}

In the above example, by the time return statement is executed, functional ponent has been rendered in view, thus React has updated the rendered state in it's virtual DOM for tracking during uping state updates. Button click event cannot occur before the ponent gets rendered in view, thus React is aware of rendered state, and can track changes in state to process re-rendering of ponent.

Component Test2:

function Test2() {
    const [name, setName] = useState("Shiva");
    // State updates before ponent gets rendered
    setName("Shiva");
    return (
        <div>
            <span>My name is {name}</span>
        </div>
    );
}

In the above example, state is updated before the ponent gets rendered into the view, thus React has not yet updated it's virtual DOM with rendered state, thus there is no rendered state to track the changes. This triggers infinite re-rendering.

本文标签: javascriptWhy React goes Infinite when I set state in function bodyStack Overflow