React Hooks render twice
Asked Answered
P

5

29

I define a scene: we have a component that uses parent's props and itself state.

There are two Components DC and JOKER and my step under the below:

  1. click DC's button
  2. DC setCount
  3. JOKER will render with the old state
  4. running useEffect and setCount
  5. JOKER does render again

enter image description here

I want to ask why JOKER render twice(step 3 and 5) and the first render squanders the performance. I just do not want step 3. If in class component I can use componentShouldUpdate to avoid it. But Hooks has the same something?

My code under the below, or open this website https://jsfiddle.net/stephenkingsley/sw5qnjg7/

import React, { PureComponent, useState, useEffect, } from 'react';

function JOKER(props) {
  const [count, setCount] = useState(props.count);
  useEffect(() => {
    console.log('I am JOKER\'s useEffect--->', props.count);
    setCount(props.count);
  }, [props.count]);

  console.log('I am JOKER\'s  render-->', count);
  return (
    <div>
      <p style={{ color: 'red' }}>JOKER: You clicked {count} times</p>
    </div>
  );
}

function DC() {
  const [count, setCount] = useState(0);
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => {
        console.log('\n');
        setCount(count + 1);
      }}>
        Click me
      </button>
      <JOKER count={count} />
    </div>
  );
}

ReactDOM.render(<DC />, document.querySelector("#app"))
Paleo answered 29/10, 2019 at 7:50 Comment(4)
Because you are updating JOKER local state using setCount(props.count); line removing that won't re-render. More on it reactjs.org/docs/hooks-effect.htmlBotzow
@ReyanshMishra I concern about the step 3. I knew when I setCount in JOKER, JOKER will render again. If in class component I can use componentShouldUpdate to avoid it. Now hooks has the same thing or api?Paleo
useEffect is set to run after the first render so that the developer gets to render something on screen before the effect runs hence increasing the user perceived performance metrics. The performance of a component is not equal to how many renders it runs.Hollo
@StephenKingsley while there were plans for this, they didn't go though with it, as it caused lots of confusion behaviour if you forgot to add a field to the watched fields, or if you are using compound hook calls that have private state.Holey
O
80

It's an intentional feature of the StrictMode. This only happens in development, and helps find accidental side effects put into the render phase. We only do this for components with Hooks because those are more likely to accidentally have side effects in the wrong place. -- gaearon commented on Mar 9, 2019

Orvalorvan answered 18/4, 2020 at 8:28 Comment(2)
I'm out here losing my mind and the answer is "it's a feature, not a bug". Either way, +1, thanks!Licko
For a more detailed answer see #72238675.Ptero
P
16

You can simply make modifications in ./index.js

change this

 <React.StrictMode>
    <App />
 </React.StrictMode>

to this

 <>
    <App />
  </>

React.StrictMode causes component render in development mode. (works in reactjs version 18.0.2)

Palocz answered 17/10, 2022 at 8:8 Comment(2)
Thank you very much your answer helped me a lot, please, could you explain why useEffect only works once when React.StrictMode is removed?Haematin
first of StrictMode is a tool for highlighting potential problems in an application. Like Fragment, StrictMode does not render any visible UI. It activates additional checks and warnings for its descendants, Strict mode checks are run in development mode only, They do not impact the production. When Strict Mode is active, all components mount and unmount before being remounted again. that's why it renders twice. Hope that helps!Palocz
B
2

I'm not sure I understand your question, but here goes.

When your <DC /> component changes state, it passes the new state value count to the component Joker. At this point the component will rerender, accounting for the first change.

Then you bind the effect to props.count changes;

  useEffect(() => {
    console.log('I am JOKER\'s useEffect--->', props.count);
    setCount(props.count); 
  }, [props.count]);// <-- This one

Which triggers when the component gets the new value from the component DC. It will set the state of it self Joker to props.count, which causes the component to rerender.

Which then gives you the following output:

I am JOKER's  render--> 1 // Initial render where Joker receives props from DC
index.js:27 I am JOKER's useEffect---> 2 // The hook runs because props.count changed
index.js:27 I am JOKER's  render--> 2 // Joker rerenders because its state updated.
Bosco answered 29/10, 2019 at 8:1 Comment(5)
Woo.that is my point! If DC's state changed, JOKER will do initiate render. Do you think this is a unnecessary render?Paleo
@StephenKingsley the lifecycle of a component in React is made in a way where it will do an initial render. If you think about it, how would it run any code, if it does not run at least once when it is mounted? If you know something like C#, think the constructor in a class that runs initially. Seeing functional components are basically inline logic and view specific to a certain logical unit that makes sense. "Figure out what to do, then show it". You can read some more about lifecycles in react here reactjs.org/docs/state-and-lifecycle.htmlBosco
@Dannis This is not about a component lifecycle. Hooks is not a class component and it not has any lifecycle. What I concern is after click button, joker does render first, next useEffect, next render again. I knew the sequence of Hooks, just I do not want the first render.Paleo
I am very aware of this, but let me ask you in another way. How will you run the code useEffect, if you do not run the functional scope in which it lies? It is not possible. It's not a "render" per se, it's simply a function. You cannot both run, and not run, a function body at the same time. That's just not how functions work. If you don't want the actual render (the DOM part), then you can gate that with a variable, but you cannot prevent the code from actually running.Bosco
If we use class component, there are shouldComponentUpdate and pureComponent. But now what we have in Hooks?Paleo
P
0

If we just want to do the same something alike componentShouldUpdate, we can use useMemo.

function DC() {
  const [count, setCount] = useState(0);
  const [sum, setSum] = useState(0);
  const memoizedJOKER = useMemo(() => <JOKER count={count} />, [count]);
  return (
    <div>
      <button onClick={() => {
        // setCount(count + 1);
        setSum(sum + 1);
        console.log('---click---');
        console.log('\n');
      }}>
        Click me
      </button>
      <p>DC: You clicked {count} times</p>
      <p>now this is {sum} times</p>
      {memoizedJOKER}
    </div>
  );
}

When you click button, JOKER does not render again.

Paleo answered 30/10, 2019 at 2:37 Comment(0)
D
0

Use the following custom useEffect code to force react to render a component once, all you need to do is import and use it in place of usEffect.

import {useRef} from 'react'

export const useEffectOnce = ( effect )=> {

  const destroyFunc = useRef();
  const effectCalled = useRef(false);
  const renderAfterCalled = useRef(false);
  const [val, setVal] = useState(0);

  if (effectCalled.current) {
      renderAfterCalled.current = true;
  }

  useEffect( ()=> {

      // only execute the effect first time around
      if (!effectCalled.current) { 
        destroyFunc.current = effect();
        effectCalled.current = true;
      }

      // this forces one render after the effect is run
      setVal(val => val + 1);

      return ()=> {
        // if the comp didn't render since the useEffect was called,
        // we know it's the dummy React cycle
        if (!renderAfterCalled.current) { return; }
        if (destroyFunc.current) { destroyFunc.current(); }
      };
  }, []);
};

For more information click here

Diophantus answered 14/6, 2022 at 9:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.