javascript – componentDidMount equivalent on a React function/Hooks component?

Yes, there is a way to SIMULATE a componentDidMount in a React functional component

DISCLAIMER: The real problem here is that you need to change from “component life cycle mindset” to a “mindset of useEffect”

A React component is still a javascript function, so, if you want something to be executed BEFORE some other thing you must simply need to execute it first from top to bottom, if you think about it a function it’s still a funtion like for example:

const myFunction = () => console.log('a')
const mySecondFunction = () => console.log('b)

mySecondFunction()
myFunction()

/* Result:
'b'
'a'
*/

That is really simple isn’t it?

const MyComponent = () => {
const someCleverFunction = () => {...}

someCleverFunction() /* there I can execute it BEFORE 
the first render (componentWillMount)*/

useEffect(()=> {
  someCleverFunction() /* there I can execute it AFTER the first render */ 
},[]) /*I lie to react saying "hey, there are not external data (dependencies) that needs to be mapped here, trust me, I will leave this in blank.*/

return (
 <div>
   <h1>Hi!</h1>
 </div>
)}

And in this specific case it’s true. But what happens if I do something like that:

const MyComponent = () => {
const someCleverFunction = () => {...}

someCleverFunction() /* there I can execute it BEFORE 
the first render (componentWillMount)*/

useEffect(()=> {
  someCleverFunction() /* there I can execute it AFTER the first render */ 
},[]) /*I lie to react saying "hey, there are not external data (dependencies) that needs to be maped here, trust me, I will leave this in blank.*/
return (
 <div>
   <h1>Hi!</h1>
 </div>
)}

This “cleverFunction” we are defining it’s not the same in every re-render of the component. This lead to some nasty bugs and, in some cases to unnecessary re-renders of components or infinite re-render loops.

The real problem with that is that a React functional component is a function that “executes itself” several times depending on your state thanks to the useEffect hook (among others).

In short useEffect it’s a hook designed specifically to synchronize your data with whatever you are seeing on the screen. If your data changes, your useEffect hook needs to be aware of that, always. That includes your methods, for that it’s the array dependencies. Leaving that undefined leaves you open to hard-to-find bugs.

Because of that it’s important to know how this work, and what you can do to get what you want in the “react” way.


const initialState = {
  count: 0,
  step: 1,
  done: false
};

function reducer(state, action) {
  const { count, step } = state;
  if (action.type === 'doSomething') {
    if(state.done === true) return state;
    return { ...state, count: state.count + state.step, state.done:true };
  } else if (action.type === 'step') {
    return { ...state, step: action.step };
  } else {
    throw new Error();
  }
}

const MyComponent = () => {
const [state, dispatch] = useReducer(reducer, initialState);
const { count, step } = state;

useEffect(() => {
    dispatch({ type: 'doSomething' });
}, [dispatch]);

return (
 <div>
   <h1>Hi!</h1>
 </div>
)}

useReducer’s dispatch method it’s static so it means it will be the same method no matter the amount of times your component is re-rendered. So if you want to execute something just once and you want it rigth after the component is mounted, you can do something like the above example. This is a declarative way of doing it right.

Source: The Complete Guide to useEffect – By Dan Abramov

That being said if you like to experiment with things and want to know how to do it “the imperative wat” you can use a useRef() with a counter or a boolean to check if that ref stores a defined reference or not, this is an imperative approach and it’s recommended to avoid it if you’re not familiar with what happens with react behind curtains.

That is because useRef() is a hook that saves the argument passed to it regardless of the amount of renders (I am keeping it simple because it’s not the focus of the problem here, you can read this amazing article about useRef). So it’s the best approach to known when the first render of the component happened.

I leave an example showing 3 different ways of synchronise an “outside” effect (like an external function) with the “inner” component state.

You can run this snippet right here to see the logs and understand when these 3 functions are executed.

const { useRef, useState, useEffect, useCallback } = React

// External functions outside react component (like a data fetch)

function renderOnce(count) {
    console.log(`renderOnce: I executed ${count} times because my default state is: undefined by default!`);
}

function renderOnFirstReRender(count) {
    console.log(`renderOnUpdate: I executed just ${count} times!`);
}

function renderOnEveryUpdate(count) {
    console.log(`renderOnEveryUpdate: I executed ${count ? count + 1 : 1} times!`);
}

const MyComponent = () => {
    const [count, setCount] = useState(undefined);

    const mounted = useRef(0);

    // useCallback is used just to avoid warnings in console.log
    const renderOnEveryUpdateCallBack = useCallback(count => {
        renderOnEveryUpdate(count);
    }, []);

    if (mounted.current === 0) {
        renderOnce(count);
    }

    if (mounted.current === 1) renderOnFirstReRender(count);

    useEffect(() => {
        mounted.current = mounted.current + 1;
        renderOnEveryUpdateCallBack(count);
    }, [count, renderOnEveryUpdateCallBack]);

    return (
        <div>
            <h1>{count}</h1>
            <button onClick={() => setCount(prevState => (prevState ? prevState + 1 : 1))}>TouchMe</button>
        </div>
    );
};

class App extends React.Component {
    render() {
        return (
            <div>
                <h1>hI!</h1>
            </div>
        );
    }
}

ReactDOM.createRoot(
    document.getElementById("root")
).render(
  <MyComponent/>
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>

If you execute it you will see something like this:

Leave a Comment