Stephen Kingsley
Stephen Kingsley

Reputation: 559

React Hooks render twice

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"))

Upvotes: 29

Views: 40478

Answers (5)

Junaid Raza
Junaid Raza

Reputation: 346

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)

Upvotes: 16

Stickman Dev.
Stickman Dev.

Reputation: 31

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

Upvotes: 0

Vijay Sharma
Vijay Sharma

Reputation: 849

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

Upvotes: 82

Stephen Kingsley
Stephen Kingsley

Reputation: 559

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.

Upvotes: 0

Dennis
Dennis

Reputation: 939

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.

Upvotes: 2

Related Questions