6

I didn't found any examples about how to fetch data from express server using react with socket.io.

Now i do something like this: Server.js

io.on('connection', socket => {
  console.log(socket.id)

  socket.on('disconnect', () => {
    console.log(socket.id + ' disconnected')
  })

  socket.on('load settings', () => {
    socket.emit('settings is here', data)
  })
})

React.js

const [socket] = useState(io())
  const [settings, setSettings] = useState(false)

   useEffect(() => {
    try {
      socket.emit('load settings');

      socket.on('settings is here', (data) => {
        // we get settings data and can do something with it
        setSettings(data)
      })
    } catch (error) {
      console.log(error)
    }
  }, [])
3
  • 1
    This looks like it would work. Is it failing? Also you don't need to pass that empty array to useEffect. It diffs variables passed into that array to decide whether or not to execute the effect callback. Commented Nov 19, 2018 at 22:09
  • 1
    yes, it's working, i just don't know is it a right way! First time trying sockets. If i will not pass empty array, it's will rerender every sec. With empty array it's like to say "do only onse". Commented Nov 19, 2018 at 22:48
  • Oh thats a good point about only wanting this to execute once. Anyways, this looks fine to me. Commented Nov 19, 2018 at 22:55

2 Answers 2

14

This looks fine, but there are some things you can improve on, such as disconnecting the socket before unmounting and not making the socket part of state (refer to the code example below).

If you're confused over how to port existing code to hooks, write out the component using classes first, then port part by part to hooks. You could refer to this StackOverflow answer as a cheatsheet.

Using traditional classes, using socket.io looks like:

class App extends React.Component {
  constructor(props) {
    super(props);
    this.socket = io();
  }

  componentDidMount() {
    this.socket.open();
    this.socket.emit('load settings');
    this.socket.on('settings is here', (data) => {
      // we get settings data and can do something with it
      this.setState({
        settings: data,
      })
    });
  }

  componentWillUnmount() {
    this.socket.close();
  }

  render() {
    ...
  }
}

Then you can port the this.socket to use useRef (it doesn't need to be part of state as your render() function doesn't need it. So useRef is a better alternative (although useState is likely to still work).

Port componentDidMount() via using useEffect and passing an empty array as the second argument to make the effect callback only run on mount.

Port componentWillUnmount() via returning a callback function in the useEffect callback which React will call before unmounting.

function App() {
  const socketRef = useRef(null);
  const [settings, setSettings] = useState(false);

  useEffect(() => {
    if (socketRef.current == null) {
      socketRef.current = io();
    }

    const {current: socket} = socketRef;

    try {
      socket.open();
      socket.emit('load settings');
      socket.on('settings is here', (data) => {
        // we get settings data and can do something with it
        setSettings(data);
      })
    } catch (error) {
      console.log(error);
    }
    // Return a callback to be run before unmount-ing.
    return () => {
      socket.close();
    };
  }, []); // Pass in an empty array to only run on mount.

  return ...;
}
Sign up to request clarification or add additional context in comments.

7 Comments

Thank you very match, i understand classes. One more question, i need to share same socket to other components that will want to fetch their own data. So, is it a good idea to just share this socket ref in provider?
You could create a top level component that communicates with the sockets and passes down the data via context. I don't think the other components should be aware and reference the socket.
This is the best example of a well-documented functional implementation with web sockets that I've seen. Thank you very much!
@James You would want to reference the same socket object throughout the component's lifetime. If you declare it as a variable within the function, a new instance gets reinitialized on every render and your socket will disconnect. Declaring outside the function is also bad as you can only have one instance for the entire page (all instances of the components will share that same socket instance.
Very good example. However, warning with that: const { current: socket } = useRef(io()); Multiple connections are created on each render (even if the socket reference remains the same). We should instantiate the socket inside the effect to avoid this bad behavior.
|
8

The accepted answer has the downside, that the initial state of the useRef() gets called on every re-render. With a text input for example, a new connection is established on every input change. I came up with two solutions:

  1. Define the socket in the useEffect
const ChatInput = () => {
  const [chatMessage, setChatMessage] = useState<string>('');
  const socket = useRef<Socket>();

  useEffect(() => {
    socket.current = io('my api');
    socket.current.on('chat message', (message: string) => {
      setChatMessage(message);
    });
    return () => { socket.current?.disconnect(); };
  }, []);

  const inputHandler = (text: string) => {
    socket.current?.emit('chat message', text);
  };

  return (
    <View>
      <Text>{chatMessage}</Text>
      <TextInput onChangeText={(text) => inputHandler(text)} />
    </View>
  );
};
  1. Define the socket.io in a useState()
const ChatInput = () => {
  const [chatMessage, setChatMessage] = useState<string>('');
  const [socket] = useState(() => io('my api'));

  useEffect(() => {
    socket.on('chat message', (message: string) => {
      setChatMessage(message);
    });
    return () => { socket.disconnect(); };
  }, []);

  const inputHandler = (text: string) => {
    socket.emit('chat message', text);
  };

  return (
    <View>
      <Text>{chatMessage}</Text>
      <TextInput onChangeText={(text) => inputHandler(text)}/>
    </View>
  );
};

export default ChatInput;

5 Comments

I voted for this response because it solves the multiple connection problem. And, I have a preference for the useRef one. useState have to be used with states that changes rendering not Refs.
@Fabian What is the reason of using useRef to setup the websocket rather than declaring it as a variable (ex. let socket = io())?
@James Read this article from the Christ of react and understand the philosophy of react.js overreacted.io/react-as-a-ui-runtime
Hi @James. Given we define the let socket in a useEffect, as soon as the component mounts (eg. useEffect(() => {socket = io()}, []}), then we still avoid the connection problem. In this case though, the socket variable will be undefined on any rerender (since it is only defined on mount). Now we store our messages in a state, which causes a rerender on state change. This is why we need to "persist" the connection. With useRef we can persist the socket throughout the lifetime of the component.
Thanks Fabian, the second solution solves my constant connections on re-render. And it works perfectly using redux with RTK as: socket.on('tickers', (tickers) => { dispatch(setTickers(tickers)) })

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.