200

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;
8
  • 3
    componentDidMount fires AFTER the component has been mounted. So, getPoints() is only called after the first render. Commented Feb 17, 2018 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 Commented Feb 17, 2018 at 21:54
  • I just add the doc section of the lifecycle of a component here. reactjs.org/docs/… Commented Feb 17, 2018 at 21:56
  • Possible duplicate of Why does a react component render twice? Commented Feb 17, 2018 at 21:57
  • There's an excellent explanation and examples in the new React documentation which is still in beta: beta.reactjs.org/learn/… Commented Jun 22, 2022 at 5:54

14 Answers 14

330

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

Sign up to request clarification or add additional context in comments.

10 Comments

Can you pls elaborate more on this.
Maybe add links to the relevant docs/articles
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
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.
thanks i remove React.StrictMode and my problem solved.
|
74

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

2 Comments

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).
Removing Strict Mode is the last thing to do. For a more in-depth answer stackoverflow.com/questions/72238175/…
21

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

Comments

16

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/

Comments

7

it is done intentionally by react to avoid this remove

  <React.StrictMode>     </React.StrictMode>

from index.js

Comments

5

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.

1 Comment

'this blog' link broken
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

Comments

1

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

Comments

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.

Comments

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.

Comments

0

For me, in next.js version 14:

// next.config.mjs

const nextConfig = {
    reactStrictMode: false,
}

export default nextConfig

Comments

0

This is happening because of what has been added in the recent React updates "ReactStrictMode", to resolve it go to the index.js file and remove this

  <React.StrictMode>     </React.StrictMode>

Comments

0

You can create UserContext, which is generated userid. Then you can check whenever userid is invalid your component will render loading

UserContext.js

import React, { createContext, useState, useEffect } from 'react';
import { v4 as uuidv4 } from 'uuid';

// Create Context
export const UserContext = createContext();

// Create a Provider Component
export const UserProvider = ({ children }) =>
{
    const [userId, setUserId] = useState(null);

    useEffect(() =>
    {
        const id = uuidv4();
        setUserId(id);
    }, []);

    return (
        <UserContext.Provider value={{ userId }}>
            {children}
        </UserContext.Provider>
    );
};

index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import { UserProvider } from './context/UserContext';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <UserProvider>
      <App />
    </UserProvider>
  </React.StrictMode>
);

MyComponent.js

import React, { useEffect, useState } from 'react';
import { UserContext } from './context/UserContext';

const MyComponent = () =>
{
    const { userId } = React.useContext(UserContext);
    const [avatarUrl, setAvatarUrl] = useState(null);
    useEffect(() =>
    {
        if (!userId)
        {
            // prevent API called twice
            return;
        }
        const url = `https://picsum.photos/seed/${userId}/200`;
        fetch(url)
            .then((response) =>
            {
                setAvatarUrl(response.url);
            });
    }, [userId]);

    if (!userId)
    {
        // prevent unexpected rendering
        return <div>Loading...</div>;
    }

    return (
        <div>
            <h1>Hello, {userId}</h1>
            <img src={avatarUrl} alt="Avatar" />
        </div>
    );
};

export default MyComponent;

App.js

import './App.css';
import MyComponent from './MyComponent';

function App()
{
  return (
    <div className="App">
        <MyComponent />
    </div>
  );
}

export default App;

Comments

0

As an option

function App () {
const [todos, setTodos] = useState<ToDoDto[]>();
const [doFetch, setDoFetch] = useState(false);  // anti twice fetch under StrictMode

useEffect(() => {
    setDoFetch(true);
}, []);

useEffect(() => {
    if (doFetch)
      populateToDoData(); // fetches once
}, [doFetch]);

return <>...</>;
}

1 Comment

Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.