Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React - useEffect hook - componentDidMount to useEffect

I would like to convert this to an useEffect hook:

CODE

componentDidMount () {
   this.messagesRef.on('child_added', snapshot => {
    const message = snapshot.val();
    message.key = snapshot.key;
    this.setState({messages: this.state.messages.concat(message 
  )});
});

UPDATED CODE

const MessageList = () => {
  const [messages, setMessage] = useState([]);
  const [usernames, setUsernames] = useState('');
  const [contents, setContents] = useState('');
  const [roomId, setRoomId] = useState('');

  const messagesRef = MessageList.props.firebase.database().ref('messages');

  useEffect(() => {
    messagesRef.on('child added', snapshot => {
    const message = snapshot.val();
    message.key = snapshot.key;

    const [messages, setMessages] = useState({messages: messages.concat(message)});
  });
 })
}

Right now it's giving me a useState cannot be used in a callback.

How can I address this or convert this properly?

like image 313
mph85 Avatar asked May 22 '19 04:05

mph85


People also ask

Is useEffect equivalent to componentDidMount?

The equivalent of componentDidMount in hooks is the useEffect function. Functions passed to useEffect are executed on every component rendering—unless you pass a second argument to it.

What is difference between useEffect and componentDidMount?

Hooks and useEffect() both run after the component is mounted. The difference is that hooks are also run after the DOM content has been painted. So, if the state is updated synchronously within an effect method users will see a flicker as the first frame is replaced with the second frame.


3 Answers

There are a couple of things there. First, to fix the code, you could update your useEffect to this:

useEffect(() => {
    messagesRef.on('child added', snapshot => {
    const message = snapshot.val();
    message.key = snapshot.key;

    setMessages(messages.concat(message)); // See Note 1
}, []); // See Note 2

Note 1

The setMessages line is how you update your state. useState is a little bit different from the "old" setState in a sense that will completely replace the state value. React documentation says:

This is because when we update a state variable, we replace its value. This is different from this.setState in a class, which merges the updated fields into the object.

Note 2

React Hooks changes the way we build apps and it is not a real "translation" from the old lifecycles.

The empty brackets ([]) in the last line, will make your code "similar" to componentDidMount, but most importantly, will make your effect run only once.

Dan Abramov said (removed some of the original text):

While you can useEffect(fn, []), it’s not an exact equivalent. Unlike componentDidMount, it will capture props and state. So even inside the callbacks, you’ll see the initial props and state. (...) Keep in mind that the mental model for effects is different from componentDidMount and other lifecycles, and trying to find their exact equivalents may confuse you more than help. To get productive, you need to “think in effects”, and their mental model is closer to implementing synchronization than to responding to lifecycle events.

Full article about useEffect here.

like image 67
Bruno Monteiro Avatar answered Oct 27 '22 01:10

Bruno Monteiro


You tried to declare the state again instead of using the state updater

useEffect(() => {
  messagesRef.on('child added', snapshot => {
    const message = snapshot.val();
    message.key = snapshot.key;
    // setMessages is the state updater for messages
    // instead of an object with messages: messagesArray
    // just save it as an array the name is already messages
    setMessages([...messages, message]);
  });
// useEffect takes an array as second argument with the dependencies
// of the effect, if one of the dependencies changes the effect will rerun
// provide an empty array if you want to run this effect only on mount
}, []);
like image 29
Asaf Aviv Avatar answered Oct 26 '22 23:10

Asaf Aviv


I found an alternative solution that doesn't require modifying html. We create a higher order component that displays some waiting element and switch the state in componentDidMount or use effect in order to render the target component.

import React, { useEffect, useState } from 'react';
const Loading = (props) => {

    const [loading, setLoading] = useState(true);

    useEffect(() => {
        setLoading(false);
    }, []);

    return (
        <>
            {loading ?
            <div style={{
                display: 'flex',
                justifyContent: 'center',
                alignItems: 'center',
                width: '100%',
                height: '100%',
                fontSize: '5vh'
            }}>
                Loading...
            </div> :
            props.children}
        </>
    );

};

export default Loading;

The disadvantage is that the animated elements are not working.

like image 34
Karol Borkowski Avatar answered Oct 27 '22 00:10

Karol Borkowski