apollo24
apollo24

Reputation: 313

Memory leak issue when trying to update state in useEffect (React Native)

I got a component which should render a chat screen, with the message coming from socket.io.

The component :

    export default function ChatScreen() {

      const [name, setName] = useState("");
      const [message, setMessage] = useState("");

      let socket;

      useEffect(() => {
        socket = io("http://192.168.1.229:3000")
        socket.on("chatMessage", messageObj => {

          console.log(`The message sender is ${messageObj.name}`)

          setName(messageObj.name)
          setMessage(messageObj.message)

        })
      });

      return (
          <Text>
            {name} : {message}
          </Text>
      );
    }

the

console.log(`The message sender is ${messageObj.name}`)

Is working but it prints multiple times. The state update after isn't working at all. I get a message on console that says :

Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in %s.%s, a useEffect cleanup function, in ChatScreen (at SceneView.js:9)

Anyone gets it?

Upvotes: 0

Views: 2609

Answers (1)

cbr
cbr

Reputation: 13660

The warning hits the nail on the head. You're opening a socket in useEffect(), but you are not returning a cleanup function from it, or passing an array of dependencies to it. The first issue means that if the component is unmounted, the socket will never be closed. The second issue means that every time the component renders, a new socket will be opened. So every time a chatMessage event occurs, yet another socket so now you have two sockets re-rendering the app every time chatMessage occurs, and so on.

To solve the first issue, return a function from the useEffect function which closes the socket. React will call this when the component unmounts or when the hook gets triggered due to changed dependencies.

To solve the second issue, pass an empty array for the dependencies, since you're not using anything passed via props in the effect hook. The empty array will prevent the effect hook from being run every time the component is re-rendered.

export default function ChatScreen() {
  const [name, setName] = useState("");
  const [message, setMessage] = useState("");

  useEffect(() => {
    const socket = io("http://192.168.1.229:3000");
    socket.on("chatMessage", messageObj => {
      console.log(`The message sender is ${messageObj.name}`);

      setName(messageObj.name);
      setMessage(messageObj.message);
    });

    return () => socket.close();
  }, []);

  return (
    <Text>
      {name} : {message}
    </Text>
  );
}

I would also recommend taking some time to read React's documentation on the useEffect hook: Using the Effect hook.

Upvotes: 3

Related Questions