[javascript] How to use componentWillMount() in React Hooks?

In the official docs of React it mentions -

If you’re familiar with React class lifecycle methods, you can think of useEffect Hook as componentDidMount, componentDidUpdate, and componentWillUnmount combined.

My question is - how can we use the componentWillMount() lifecyle method in a hook?

This question is related to javascript reactjs jsx react-hooks

The answer is


As it has been stated in react document:

You might be thinking that we’d need a separate effect to perform the cleanup. But code for adding and removing a subscription is so tightly related that useEffect is designed to keep it together. If your effect returns a function, React will run it when it is time to clean up:

useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    // Specify how to clean up after this effect:
    return function cleanup() {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}

so the only thing that we need to have the componentWillUnmount in hooks is to return a function inside a useEffect, as explained above.


https://reactjs.org/docs/hooks-reference.html#usememo

Remember that the function passed to useMemo runs during rendering. Don’t do anything there that you wouldn’t normally do while rendering. For example, side effects belong in useEffect, not useMemo.


There is a nice workaround to implement componentDidMount and componentWillUnmount with useEffect.

Based on the documentation, useEffect can return a "cleanup" function. this function will not be invoked on the first useEffect call, only on subsequent calls.

Therefore, if we use the useEffect hook with no dependencies at all, the hook will be called only when the component is mounted and the "cleanup" function is called when the component is unmounted.

useEffect(() => {
    console.log('componentDidMount');

    return () => {
        console.log('componentWillUnmount');
    };
}, []);

The cleanup return function call is invoked only when the component is unmounted.

Hope this helps.


It might be clear for most, but have in mind that a function called inside the function component's body, acts as a beforeRender. This doesn't answer the question of running code on ComponentWillMount (before the first render) but since it is related and might help others I'm leaving it here.

const MyComponent = () => {
  const [counter, setCounter] = useState(0)
  
  useEffect(() => {
    console.log('after render')
  })

  const iterate = () => {
    setCounter(prevCounter => prevCounter+1)
  }

  const beforeRender = () => {
    console.log('before render')
  }

  beforeRender()

  return (
    <div>
      <div>{counter}</div>
      <button onClick={iterate}>Re-render</button>
    </div>
  )
}

export default MyComponent

You can hack the useMemo hook to imitate a componentWillMount lifecycle event. Just do:

const Component = () => {
   useMemo(() => {
     // componentWillMount events
   },[]);
   useEffect(() => {
     // componentDidMount events
     return () => {
       // componentWillUnmount events
     }
   }, []);
};

You would need to keep the useMemo hook before anything that interacts with your state. This is not how it is intended but it worked for me for all componentWillMount issues.

This works because useMemo doesnt require to actually return a value and you dont have to actually use it as anything, but since it memorizes a value based on dependencies which will only run once ("[]") and its on top of our component it runs once when the component mounts before anything else.


Just simply add an empty dependenncy array in useEffect it will works as componentDidMount.

useEffect(() => {
  // Your code here
  console.log("componentDidMount")
}, []);

useComponentWillMount hook

export const useComponentWillMount = (func) => {
    const willMount = useRef(true)

    if (willMount.current) func()

    willMount.current = false
}

// or
export const useComponentWillMount = (func) => {
    useMemo(func, [])
}

Discussion

In class components componentWillMount is considered legacy (source 1, source2). However, this shouldn't apply to functional components and a hook based solution. Class component componentWillMount is deprecated since it might run more than once, and there is an alternative - using the constructor. Those considerations aren't relevant for a functional component.

My experience is that such a hook could be a saver when timing/sequence is critical. I'm interested to know what is your use case - comments are welcome.

useComponentDidMount hook

Alternatively, use componentDidMount hook.

const useComponentDidMount = func => useEffect(func, []);

It will run only once, after component has mounted(initial render to the dom).

Demo

const Component = (props) => {
  useComponentWillMount(() => console.log("Runs only once before component mounts"));
  useComponentDidMount(() => console.log("Runs only once after component mounts"));
  ...

  return (
    <div>{...}</div>
  );
}

Full Demo


According to reactjs.org, componentWillMount will not be supported in the future. https://reactjs.org/docs/react-component.html#unsafe_componentwillmount

There is no need to use componentWillMount.

If you want to do something before the component mounted, just do it in the constructor().

If you want to do network requests, do not do it in componentWillMount. It is because doing this will lead to unexpected bugs.

Network requests can be done in componentDidMount.

Hope it helps.


updated on 08/03/2019

The reason why you ask for componentWillMount is probably because you want to initialize the state before renders.

Just do it in useState.

const helloWorld=()=>{
    const [value,setValue]=useState(0) //initialize your state here
    return <p>{value}</p>
}
export default helloWorld;

or maybe You want to run a function in componentWillMount, for example, if your original code looks like this:

componentWillMount(){
  console.log('componentWillMount')
}

with hook, all you need to do is to remove the lifecycle method:

const hookComponent=()=>{
    console.log('componentWillMount')
    return <p>you have transfered componeWillMount from class component into hook </p>
}

I just want to add something to the first answer about useEffect.

useEffect(()=>{})

useEffect runs on every render, it is a combination of componentDidUpdate, componentDidMount and ComponentWillUnmount.

 useEffect(()=>{},[])

If we add an empty array in useEffect it runs just when the component mounted. It is because useEffect will compare the array you passed to it. So it does not have to be an empty array.It can be array that is not changing. For example, it can be [1,2,3] or ['1,2']. useEffect still only runs when component mounted.

It depends on you whether you want it to run just once or runs after every render. It is not dangerous if you forgot to add an array as long as you know what you are doing.

I created a sample for hook. Please check it out.

https://codesandbox.io/s/kw6xj153wr


updated on 21/08/2019

It has been a while since I wrote the above answer. There is something that I think you need to pay attention to. When you use

useEffect(()=>{},[])

When react compares the values you passed to the array [], it uses Object.is() to compare. If you pass an object to it, such as

useEffect(()=>{},[{name:'Tom'}])

This is exactly the same as:

useEffect(()=>{})

It will re-render every time because when Object.is() compares an object, it compares its reference, not the value itself. It is the same as why {}==={} returns false because their references are different. If you still want to compare the object itself not the reference, you can do something like this:

useEffect(()=>{},[JSON.stringify({name:'Tom'})])

React Lifecycle methods in hooks

for simple visual reference follow this image

enter image description here

As you can simply see in the above picture that for ComponentWillUnmount, you have to do like this

 useEffect(() => {
    return () => {
        console.log('componentWillUnmount');
    };
   }, []);

Short answer to your original question, how componentWillMount can be used with React Hooks:

componentWillMount is deprecated and considered legacy. React recommendation:

Generally, we recommend using the constructor() instead for initializing state.

Now in the Hook FAQ you find out, what the equivalent of a class constructor for function components is:

constructor: Function components don’t need a constructor. You can initialize the state in the useState call. If computing the initial state is expensive, you can pass a function to useState.

So a usage example of componentWillMount looks like this:

const MyComp = () => {
  const [state, setState] = useState(42) // set initial value directly in useState 
  const [state2, setState2] = useState(createInitVal) // call complex computation

  return <div>{state},{state2}</div>
};

const createInitVal = () => { /* ... complex computation or other logic */ return 42; };

Ben Carp's answer seems like only valid one to me.

But since we are using functional ways just another approach can be benefiting from closure and HoC:

const InjectWillmount = function(Node, willMountCallback) {
  let isCalled = true;
  return function() {
    if (isCalled) {
      willMountCallback();
      isCalled = false;
    }
    return Node;
  };
};

Then use it :

const YourNewComponent = InjectWillmount(<YourComponent />, () => {
  console.log("your pre-mount logic here");
});

useLayoutEffect could accomplish this with an empty set of observers ([]) if the functionality is actually similar to componentWillMount -- it will run before the first content gets to the DOM -- though there are actually two updates but they are synchronous before drawing to the screen.

for example:


function MyComponent({ ...andItsProps }) {
     useLayoutEffect(()=> {
          console.log('I am about to render!');
     },[]);

     return (<div>some content</div>);
}

The benefit over useState with an initializer/setter or useEffect is though it may compute a render pass, there are no actual re-renders to the DOM that a user will notice, and it is run before the first noticable render, which is not the case for useEffect. The downside is of course a slight delay in your first render since a check/update has to happen before painting to screen. It really does depend on your use-case, though.

I think personally, useMemo is fine in some niche cases where you need to do something heavy -- as long as you keep in mind it is the exception vs the norm.


I wrote a custom hook that will run a function once before first render.

useBeforeFirstRender.js

import { useState, useEffect } from 'react'

export default (fun) => {
  const [hasRendered, setHasRendered] = useState(false)

  useEffect(() => setHasRendered(true), [hasRendered])

  if (!hasRendered) {
    fun()
  }
}

Usage:

import React, { useEffect } from 'react'
import useBeforeFirstRender from '../hooks/useBeforeFirstRender'


export default () => { 
  useBeforeFirstRender(() => {
    console.log('Do stuff here')
  })

  return (
    <div>
      My component
    </div>
  )
}

This is the way how I simulate constructor in functional components using the useRef hook:

function Component(props) {
    const willMount = useRef(true);
    if (willMount.current) {
        console.log('This runs only once before rendering the component.');
        willMount.current = false;        
    }

    return (<h1>Meow world!</h1>);
}

Here is the lifecycle example:

function RenderLog(props) {
    console.log('Render log: ' + props.children);
    return (<>{props.children}</>);
}

function Component(props) {

    console.log('Body');
    const [count, setCount] = useState(0);
    const willMount = useRef(true);

    if (willMount.current) {
        console.log('First time load (it runs only once)');
        setCount(2);
        willMount.current = false;
    } else {
        console.log('Repeated load');
    }

    useEffect(() => {
        console.log('Component did mount (it runs only once)');
        return () => console.log('Component will unmount');
    }, []);

    useEffect(() => {
        console.log('Component did update');
    });

    useEffect(() => {
        console.log('Component will receive props');
    }, [count]);


    return (
        <>
        <h1>{count}</h1>
        <RenderLog>{count}</RenderLog>
        </>
    );
}
[Log] Body
[Log] First time load (it runs only once)
[Log] Body
[Log] Repeated load
[Log] Render log: 2
[Log] Component did mount (it runs only once)
[Log] Component did update
[Log] Component will receive props

Of course Class components don't have Body steps, it's not possible to make 1:1 simulation due to different concepts of functions and classes.


Examples related to javascript

need to add a class to an element How to make a variable accessible outside a function? Hide Signs that Meteor.js was Used How to create a showdown.js markdown extension Please help me convert this script to a simple image slider Highlight Anchor Links when user manually scrolls? Summing radio input values How to execute an action before close metro app WinJS javascript, for loop defines a dynamic variable name Getting all files in directory with ajax

Examples related to reactjs

Error: Node Sass version 5.0.0 is incompatible with ^4.0.0 TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string. Received type undefined raised when starting react app Template not provided using create-react-app How to resolve the error on 'react-native start' Element implicitly has an 'any' type because expression of type 'string' can't be used to index Invalid hook call. Hooks can only be called inside of the body of a function component How to style components using makeStyles and still have lifecycle methods in Material UI? React Hook "useState" is called in function "app" which is neither a React function component or a custom React Hook function How to fix missing dependency warning when using useEffect React Hook? Unable to load script.Make sure you are either running a Metro server or that your bundle 'index.android.bundle' is packaged correctly for release

Examples related to jsx

TypeScript and React - children type? How to use componentWillMount() in React Hooks? expected assignment or function call: no-unused-expressions ReactJS You should not use <Link> outside a <Router> ReactJS - .JS vs .JSX React: Expected an assignment or function call and instead saw an expression React-Native Button style not work ReactJs: What should the PropTypes be for this.props.children? ReactJS map through Object Console logging for react?

Examples related to react-hooks

Invalid hook call. Hooks can only be called inside of the body of a function component React Hook "useState" is called in function "app" which is neither a React function component or a custom React Hook function How to fix missing dependency warning when using useEffect React Hook? react hooks useEffect() cleanup for only componentWillUnmount? How to use callback with useState hook in react Push method in React Hooks (useState)? React Hooks useState() with Object useState set method not reflecting change immediately React hooks useState Array Can I set state inside a useEffect hook