148

I don't know why my React component is rendering twice. So I am pulling a phone number from params and saving it to state so I can search through Firestore. Everything seems to be working fine except it renders twice... The first one renders the phone number and zero points. The second time it renders all the data is displayed correctly. Can someone guide me to the solution.

class Update extends Component {
constructor(props) {
    super(props);
    const { match } = this.props;
    this.state = {
        phoneNumber: match.params.phoneNumber,
        points: 0,
        error: ''
    }
}

getPoints = () => {
    firebase.auth().onAuthStateChanged((user) => {
        if(user) {
            const docRef = database.collection('users').doc(user.uid).collection('customers').doc(this.state.phoneNumber);
            docRef.get().then((doc) => {
                if (doc.exists) {
                const points = doc.data().points;
                this.setState(() => ({ points }));
                console.log(points);
                } else {
                // doc.data() will be undefined in this case
                console.log("No such document!");
                const error = 'This phone number is not registered yet...'
                this.setState(() => ({ error }));
                }
                }).catch(function(error) {
                console.log("Error getting document:", error);
                });
        } else {
            history.push('/')
        }
    });
}

componentDidMount() {
    if(this.state.phoneNumber) {
        this.getPoints();
    } else {
        return null;
    }
}

render() {
    return (
        <div>
            <div>
                <p>{this.state.phoneNumber} has {this.state.points} points...</p>
                <p>Would you like to redeem or add points?</p>
            </div>
            <div>
                <button>Redeem Points</button>
                <button>Add Points</button>
            </div>
        </div>
    );
  }
}

export default Update;
Raul Sanchez
  • 1,549
  • 3
  • 12
  • 7
  • 3
    `componentDidMount` fires AFTER the component has been mounted. So, `getPoints()` is only called after the first render. – Nsevens Feb 17 '18 at 21:54
  • 2
    Well, your component doesn't have all the data yet when it first mounts, only when it has mounted, you are loading the data and updating the state, so it will first render without the data and then render again with the fetched data is added to the state – Icepickle Feb 17 '18 at 21:54
  • I just add the doc section of the lifecycle of a component here. https://reactjs.org/docs/state-and-lifecycle.html#adding-lifecycle-methods-to-a-class – Striped Feb 17 '18 at 21:56
  • Possible duplicate of [Why does a react component render twice?](https://stackoverflow.com/questions/44637808/why-does-a-react-component-render-twice) – Striped Feb 17 '18 at 21:57
  • There's an excellent explanation and examples in the new React documentation which is still in beta: https://beta.reactjs.org/learn/synchronizing-with-effects#how-to-handle-the-effect-firing-twice-in-development – Samuli Asmala Jun 22 '22 at 05:54
  • For a more in-depth answer https://stackoverflow.com/questions/72238175/why-useeffect-running-twice-and-how-to-handle-it-well-in-react/72238236#72238236 – Youssouf Oumar Dec 14 '22 at 10:03
  • I have the same issue. haven't found any solution yet? in production after reload page apis will fetch twice and I tried a lot but it didn't work but when a page is ssr the problem won't occur, confusing ;( – Samira Aug 09 '23 at 09:34

10 Answers10

256

You are running your app in strict mode. Go to index.js and comment strict mode tag. You will find a single render.

This happens is an intentional feature of the React.StrictMode. It only happens in development mode and should help to find accidental side effects in the render phase.

From the docs:

Strict mode can’t automatically detect side effects for you, but it can help you spot them by making them a little more deterministic. This is done by intentionally double-invoking the following functions:...

^ In this case the render function.

Official documentation of what might cause re-rendering when using React.StrictMode:

https://reactjs.org/docs/strict-mode.html#detecting-unexpected-side-effects

Shariq Ansari
  • 3,941
  • 1
  • 27
  • 30
  • 4
    Can you pls elaborate more on this. – Sai Saagar Sharman Jan 01 '21 at 02:02
  • 1
    Maybe add links to the relevant docs/articles – lordvcs Feb 15 '21 at 08:13
  • 1
    Not sure why but this was the case for me. Removed strict case and the double renders stopped! I will re activate it tho now that i know that is not a bug – Loïc V Nov 28 '21 at 12:56
  • 1
    It's not a bug. Indeed the very reason they do the extra render is to "catch bugs" in development. (The extra render won't happen in prod.) If the extra render breaks your component, it means your component has a bug (most probably) according to the react team's philosophy. – aderchox Jun 04 '22 at 21:24
  • thanks i remove React.StrictMode and my problem solved. – GBg Jul 16 '22 at 11:27
  • One can avoid modifying `index.js` and run their app in production by first running `npm run build` followed by `npm install -g serve` and `serve -s build`. – Rm4n Jul 22 '22 at 11:31
  • `StrictMode` is not the reason in this case. (It's often the reason, just not in this case.) The OP's code changes state in a `componentDidMount` handler, so of course the component renders twice: Once before `componentDidMount`, then again after the state changes, as described [in this answer](https://stackoverflow.com/a/48846630/157247). – T.J. Crowder Aug 08 '22 at 12:19
  • Thanks. I was going crazy looking for that double render! Because of that, all my API calls are happening twice!! Also, some components are not getting unmounted properly which have "global" code in them (e.g. a `setInterval`). Removing `React.StrictMode` solved this issue. I guess I will have to keep it off for the entirety of my development time. – cst1992 Sep 10 '22 at 15:53
  • The problem is, my `useEffect(()=>..., [])` hook is called twice... So I'm getting initial render side effects going crazy - that wouldn't have happened in a `componentDidMount` – Tobiq Sep 20 '22 at 20:19
  • For a more in-depth answer https://stackoverflow.com/questions/72238175/why-useeffect-running-twice-and-how-to-handle-it-well-in-react/72238236#72238236 – Youssouf Oumar Dec 14 '22 at 10:03
48

This is because of React Strict Mode code.

Remove -> React.StrictMode, from ReactDOM.render code.

Will render 2 times on every re-render:

ReactDOM.render(
  <React.StrictMode>
<App />
  </React.StrictMode>,
  document.getElementById('root')
);

Will render 1 time:

ReactDOM.render(
  <>
<App />
  </>,
  document.getElementById('root')
);
fruitloaf
  • 1,628
  • 15
  • 10
  • 1
    important side note: **strict mode only renders twice in development mode**. So the extra API requests would only happen in development mode (usually just a couple developers). Not in production (many potential visitors). – Flion Nov 29 '22 at 11:14
  • 3
    Removing Strict Mode is the last thing to do. For a more in-depth answer https://stackoverflow.com/questions/72238175/why-useeffect-running-twice-and-how-to-handle-it-well-in-react/72238236#72238236 – Youssouf Oumar Dec 14 '22 at 10:04
20

React is rendering the component before getPoints finishing the asynchronous operation.

So the first render shows the initial state for points which is 0, then componentDidMount is called and triggers the async operation.
When the async operation is done and the state been updated, another render is triggered with the new data.

If you want, you can show a loader or an indicator that the data is being fetched and is not ready yet to display with conditional rendering.

Just add another Boolean key like isFetching, set it to true when you call the server and set it to false when the data is received.

Your render can look something like this:

  render() {
    const { isFetching } = this.state;
    return (
      <div>
        {isFetching ? (
          <div>Loading...</div>
        ) : (
          <div>
            <p>
              {this.state.phoneNumber} has {this.state.points} points...
            </p>
            <p>Would you like to redeem or add points?</p>
            <div>
              <button>Redeem Points</button>
              <button>Add Points</button>
            </div>
          </div>
        )}
      </div>
    );
  }
Sagiv b.g
  • 30,379
  • 9
  • 68
  • 99
12

React.StrictMode, makes it render twice, so that we do not put side effects in following locations

constructor
componentWillMount (or UNSAFE_componentWillMount)
componentWillReceiveProps (or UNSAFE_componentWillReceiveProps)
componentWillUpdate (or UNSAFE_componentWillUpdate)
getDerivedStateFromProps
shouldComponentUpdate
render
setState updater functions (the first argument)

All these methods are called more than once, so it is important to avoid having side-effects in them. If we ignore this principle it is likely to end up with inconsistent state issues and memory leaks.

React.StrictMode cannot spot side-effects at once, but it can help us find them by intentionally invoking twice some key functions.

These functions are:

Class component constructor, render, and shouldComponentUpdate methods
Class component static getDerivedStateFromProps method
Function component bodies
State updater functions (the first argument to setState)
Functions passed to useState, useMemo, or useReducer

This behaviour definitely has some performance impact, but we should not worry since it takes place only in development and not in production.
credit: https://mariosfakiolas.com/blog/my-react-components-render-twice-and-drive-me-crazy/

Akshay Vijay Jain
  • 13,461
  • 8
  • 60
  • 73
5

it is done intentionally by react to avoid this remove

  <React.StrictMode>     </React.StrictMode>

from index.js

2

I worked around this by providing a custom hook. Put the hook below into your code, then:

// instead of this:
useEffect( ()=> {
    console.log('my effect is running');
    return () => console.log('my effect is destroying');
}, []);

// do this:
useEffectOnce( ()=> {
    console.log('my effect is running');
    return () => console.log('my effect is destroying');
});

Here is the code for the hook:

export const useEffectOnce = ( effect => {

    const destroyFunc = useRef();
    const calledOnce = useRef(false);
    const renderAfterCalled = useRef(false);

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

    useEffect( () => {
        if (calledOnce.current) { 
            return; 
        }

        calledOnce.current = true;
        destroyFunc.current = effect();

        return ()=> {
            if (!renderAfterCalled.current) {
                return;
            }

            if (destroyFunc.current) {
                destroyFunc.current();
            }
        };
    }, []);
};

See this blog for the explanation.

Niall Crosby
  • 329
  • 1
  • 4
1

Well, I have created a workaround hook for this. Check this, if it helps:

import { useEffect } from "react";

const useDevEffect = (cb, deps) => {
  let ran = false;
  useEffect(() => {
    if (ran) return;
    cb();
    return () => (ran = true);
  }, deps);
};

const isDev = !process.env.NODE_ENV || process.env.NODE_ENV === "development";

export const useOnceEffect = isDev ? useDevEffect : useEffect;

CodeSandbox Demo: https://github.com/akulsr0/react-18-useeffect-twice-fix

0

React internally monitors & manages its render cycles using its virtual dom and its diffing algorithms, so you need not worry about the number of re-renders. Let the re-renders to be managed by react. Even though the render function is getting invoked, there are sub components which doesn't gets refreshed on ui, if there is no props or state change inside it. Every setstate function call will inform react to check the diffing algorithm, and invoke the render function.

So in your case, since you have a setstate defined inside the getPoints function, it tells react to rerun the diffing process through the render function.

Vijay122
  • 884
  • 8
  • 12
0

Besides react StrictMode, another potential reason is using next.js rewrites which is an 'expected behavior'

fisch
  • 98
  • 6
0

I've noticed this behavior after rewriting some of my components to use props. I broke how the pages function by doing this. I'm in the process of fixing my use of props, but if I remove them from my child components, the double of my component disappears and the actual copy works with the new process I am using.