'Why is my React component is rendering twice?

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;


Solution 1:[1]

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

Solution 2:[2]

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>
    );
  }

Solution 3:[3]

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')
);

PS no offence to people that are saying DONT worry about 'how many times it re-renders' ... if you are running a perpetual API fetch code every 1 second through a setTimeout and every time you use the API it costs you 0.01 cent, every single time it re-renders it fires the setTimeout function 2 times (which means you are doubling the calls every second), which means after 5 seconds you are running 1000+ setTimeouts each calling API at the same time and after 1 hour, this number will become a trillion+, so costs will become astranomical if you get it wrong. This issue is fixed by removing React.StrictMode, so code will WAI.

Solution 4:[4]

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/

Solution 5:[5]

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.

Solution 6:[6]

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.

Solution 7:[7]

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

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Gabriel N.
Solution 2
Solution 3 fruitloaf
Solution 4 Akshay Vijay Jain
Solution 5 Vijay122
Solution 6
Solution 7