Search code examples
reactjsunsafe

How to properly get rid of UNSAFE_componentWillMount


For a React app that I inherited from another developer, one of the pages includes:

import { getLogUser } from "../../appRedux/actions/authAction";

constructor(props) {
    super(props);
    this.state = {
        user: null,
    };
}

UNSAFE_componentWillMount() {
    let user = getLogUser();
    this.setState({ user });
    // user state is used inside the render part
}

componentDidMount = () => {
    let { username } = getLogUser();
    // ... username is used inside some logic within the componentDidMount method.

I would like to get rid of the UNSAFE_componentWillMount method.

  1. Can I remove the UNSAFE_componentWillMount part if I use user: getLogUser() inside the constructor?
  2. If that is indeed the correct way to do it, shouldn't I then also replace let { username } = getLogUser(); inside componentDidMount with let { username } = this.state.user?

Solution

  • To start, let me explain what is UNSAFE_componentWillMount first

    By defination

    UNSAFE_componentWillMount() is invoked just before mounting occurs. It is called before render(), therefore calling setState() synchronously in this method will not trigger an extra rendering.

    So it means UNSAFE_componentWillMount() will be called before render() (the component has not been on UI yet). This is totally opposite of componentDidMount() which is called after render()

    To go deeper into why React's team wanted to make it UNSAFE as for a deprecated function, you can check this RFC.

    Following up on your questions

    Can I remove the UNSAFE_componentWillMount part if I use user: getLogUser() inside the constructor?

    The benefit to having your function calls in the constructor is similar to UNSAFE_componentWillMount which makes sure your data available before rendering trigger.

    So I'd say yes for your case, you can do it as long as it's not an asynchronous function (like async/await)

    constructor(props) {
        super(props);
        this.state = {
            user: await getLogUser(), //YOU CANNOT DO THIS WAY
        };
    }
    

    This is the correct way

    constructor(props) {
        super(props);
        this.state = {
            user: getLogUser(), //no asynchronous call
        };
    }
    

    So what if getLogUser() is asynchronous? componentDidMount comes in handy. It will be triggered after first rendering but you can wait for your data as much as you want and beyond that, it won't block your UI's interactions (or you can show a loading UI instead)

    componentDidMount = async () => {
       const user = await getLogUser()
       setState({ user })
    }
    
    render() {
      //show loading if `user` data is not populated yet
      const { user } = this.state
      if(!user) {
         return <div>Loading</div>
      }
    }
    

    If that is indeed the correct way to do it, shouldn't I then also replace let { username } = getLogUser(); inside componentDidMount with let { username } = this.state.user?

    Yes, indeed. You can do it if you already populate user state in constructor, but you need to ensure your function will be executed in a small amount of time. If your function call takes too long, that will cause UI problems due to the blocked rendering.

    //trigger before first rendering
    constructor(props) {
        super(props);
        this.state = {
            user: getLogUser(), //no asynchronous call
        };
    }
    
    //trigger after first rendering
    componentDidMount = () => {
        const { username } = this.state.user;
    }