What can I use in functional components to have same behavior as componentDidMount?

Viewed 73

My UI was working fine until it was using a class component. Now I am refactoring it to a functional component.

I have to load my UI based on the data I receive from an API handler. My UI will reflect the state of the camera which is present inside a room. Every time the camera is turned on or off from the room, I should receive the new state from the API apiToGetCameraState.

I want the console.log present inside the registerVideoStateUpdateHandlerWrapper to print both on UI load for the first time and also to load every time the video state is changed in the room. However, it doesn't work when the UI is loaded for the first time.

This is how my component looks like:

const Home: React.FunctionComponent<{}> = React.memo(() => {
  const [video, setToggleVideo] = React.useState(true);

  const registerVideoStateUpdateHandlerWrapper = React.useCallback(() => {
    apiToGetCameraState(
      (videoState: boolean) => {
        // this log does not show up when the UI is loaded for the first time
        console.log(
          `Video value before updating the state: ${video} and new state is: ${videoState} `
        );
        setToggleVideo(videoState);
      }
    );
  }, [video]);

  React.useEffect(() => {
    //this is getting called when the app loads
    alert(`Inside use effect for Home component`);
    registerVideoStateUpdateHandlerWrapper ();
  }, [registerVideoStateUpdateHandlerWrapper ]);

  return (
      <Grid>
        <Camera
          isVideoOn={video}
        />
      </Grid>
  );
});

This was working fine when my code was in class component. This is how the class component looked like.

class Home extends Component {
    
    registerVideoStateUpdateHandlerWrapper = () => {
        apiToGetCameraState((videoState) => {
            console.log(`ToggleVideo value before updating the state: ${this.state.toggleCamera} and new state is: ${videoState}`);
            this.setStateWrapper(videoState.toString());
        })
    }
    setStateWrapper = (toggleCameraUpdated) => {
        console.log("Inside setStateWrapper with toggleCameraUpdated:" + toggleCameraUpdated);
        this.setState({
            toggleCamera: (toggleCameraUpdated === "true" ) ? "on" : "off",
        });
    }
   
    constructor(props) {
        super(props);
        this.state = {
            toggleCamera: false,
        };
    }
    componentDidMount() {
        console.log(`Inside componentDidMount with toggleCamera: ${this.state.toggleCamera}`)
        this.registerVideoStateUpdateHandlerWrapper ();   
    }
    render() {
      return (
        <div>
          <Grid>
            <Camera isVideoOn={this.state.toggleCamera} />
          </Grid>
     );
  }
}

What all did I try?

  1. I tried removing the useCallback in the registerVideoStateUpdateHandlerWrapper function and also the dependency array from React.useEffect and registerVideoStateUpdateHandlerWrapper. It behaved the same
  2. I tried updating the React.useEffect to have the code of registerVideoStateUpdateHandlerWrapper in it but still no success.
3 Answers

Move registerVideoStateUpdateHandlerWrapper() inside the useEffect() callback like this. If you want to log the previous state when the state changes, you should use a functional update to avoid capturing the previous state through the closure:

const Home = () => {
  const [video, setVideo] = useState(false);

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

    const registerVideoStateUpdateHandlerWrapper = () => {
      apiToGetCameraState((videoState) => {
        setVideo((prevVideo) => {
          console.log(`Video value before updating the state: ${prevVideo} and new state is: ${videoState}`);
          return videoState;
        });
      });
    };

    registerVideoStateUpdateHandlerWrapper();
  }, []);

  return (
    <Grid>
      <Camera isVideoOn={video} />
    </Grid>
  );
};

When you no longer actually need to log the previous state, you should simplify registerVideoStateUpdateHandlerWrapper() to:

const registerVideoStateUpdateHandlerWrapper = () => { 
  apiToGetCameraState((videoState) => {
    setVideo(videoState);
  });
};
import React from 'react'

const Home = () => {
    const [video, setVideo] = useState(null);
    //default video is null, when first load video will change to boolean, when the Camera component will rerender
    const registerVideoStateUpdateHandlerWrapper = () => {
        apiToGetCameraState((videoState) => {
            setVideo(videoState);
        });
    };

    useEffect(() => {
      registerVideoStateUpdateHandlerWrapper();
    }, []);
  
    return (
      <Grid>
        <Camera isVideoOn={video} />
      </Grid>
    );
  };

  export default Home
componentDidMount() === useEffect()

'useEffect' => import from 'react'

// componentDidMount()
useEffect(() => {
// Implement your code here
}, []) 

// componentDidUpdate()
useEffect(() => {
// Implement your code here
}, [ update based on the props, state in here if you mention ])

e.g:

const [loggedIn, setLoggedIn] = useState(false);
useEffect(() => {
// Implement the code here
}, [ loggedIn ]);

the above code will act as equivalent to the componentDidUpdate based on 'loggedIn' state

Related