1

I know how functions are closure in Javascript and how they can access variables declared in their parent scope. Here I have defined a variable named socket in a functional component

function someName() {
    const ENDPOINT = "localhost:5000";
    let socket

    useEffect(() => {
          socket = io(ENDPOINT)
      // and some code
    }, [])

   const handleClick = () => {
         socket.emit('someEvent', someMessage)  // error: cannot read property emit of undefined
         }
   }

   return (
        <input />
        <button onClick={handleClick}
         )

when the component mounts, useEffect is called and defines the socket variable and when the function runs the variable is already defined. why am I getting that error on the handleClick function?

Heretic Monkey
  • 11,687
  • 7
  • 53
  • 122
Kanuor
  • 55
  • 5

2 Answers2

0

useEffect runs every time its dependencies change. In your case, the dependency is [], meaning it will never change, this the useEffect will never run more than once.

Therefore, every time your component re-renders, a new socket variable is created, and because the useEffect is never run again, the variable stays undefined.

What you need to do is to make sure the same socket is returned on every re-render. The easiest and the recommended way to do it is to use useRef. The useRef hook ensures that the same reference to a value is returned on every re-render.

You code should look something like this:

import React, { useEffect, useRef } from 'react';
import io from 'something';

function someName() {
  const ENDPOINT = "localhost:5000";
  const socket = useRef(null);

  useEffect(() => {
    socket.current = io(ENDPOINT);
    // and some code
  }, [])

  const handleClick = () => {
    socket.current.emit('someEvent', someMessage);
  };

  return (
    <>
      <input />
      <button onClick={handleClick}
    </>
  );
}
yqlim
  • 6,898
  • 3
  • 19
  • 43
0

The problem here is that on next render (after the on mount callback), socket value is redefined and its value is undefined.

Possible fix should like like so:

import React, { useEffect } from "react";
import io from "socket.io";

const ENDPOINT = "...";
let socket = io(ENDPOINT);

function Component() {
  const handleClick = () => {
    socket.emit("someEvent", "some message");
  };

  return (
    <>
      <input />
      <button onClick={handleClick} />
    </>
  );
}

Notice that socket.io should have a single instance (global variable/singleton), such instance should exposed in other file (export it), then every component should use it independently by improting the instance.

Dennis Vash
  • 50,196
  • 9
  • 100
  • 118
  • 2
    Why does defining the variables outside of the component help? What happens if multiple `Component`s are rendered on a page? (These are not meant to be challenges... they're just questions) – evolutionxbox Oct 06 '20 at 13:23
  • You should have a single instance of `socket.io`, such instance should exposed in other file (export it), then every component should `import` it and use independently. – Dennis Vash Oct 06 '20 at 13:25
  • Refer to https://stackoverflow.com/questions/57444154/why-need-useref-to-contain-mutable-variable-but-not-define-variable-outside-the/57444430#57444430 for differences between `useRef` and outer scope variables. – Dennis Vash Oct 06 '20 at 13:26
  • There is no hard fast rule here, there is nothing wrong in using useEffect for mounting a socket.io connection. In some ways it may even have benefits,. eg. In a SPA there maybe only a few pages that require socket.io connection, so having it as a component could save resources at both client & server. After all a socket.io connection that does nothing still takes resources. – Keith Oct 06 '20 at 13:58
  • How it has to do with what I said? You can close connection on unmount, still you have a single instance – Dennis Vash Oct 06 '20 at 14:18