R.Romero
R.Romero

Reputation: 161

Strange behavior Socket.io and React with hooks

I'm currently learning Socket.io with React with Hooks and im trying to make a timer that when you press the start button the timer starts and each second send to the server with socket.io the time that is in the state.

The server gets the correct time and I send a response back to the client.

The issue is when I console.log that response in the client it gets the response multiple times EXAMPLE

multiple console logs example

This is my index.js (NODE)

const express = require('express');
const app = express();
const socket = require('socket.io');

server = app.listen(8080, function(){
    console.log('Server is running on port 8080')
});

io = socket(server);

io.on('connection', (socket) => {
    socket.on('SEND_TIME', function(data){
        //here the data object is correct
        socket.emit('RECEIVE_MESSAGE', data); //sending back to client
    })
});

This is my React App

App.js

import React from "react";
import io from "socket.io-client";
import { Route, Switch } from "react-router-dom";
import TimerComponent from "./components/Timer";

const App = () => {
  const socket = io("localhost:8080");
  return (
    <React.Fragment>
      <Switch>
        <Route path="/" exact render={(routeProps)=> <TimerComponent {...routeProps} io={socket} />
        } />
      </Switch>
    </React.Fragment>
  );
};

export default App;

Timer component

import React, { useEffect, useState } from "react";
import { useStopwatch } from "react-timer-hook";

export default function Timer({io}) {
  const [isActive, setIsActive] = useState(false);
  const { seconds, minutes, hours, start, pause, reset } = useStopwatch({
    autoStart: false
  });

  io.on('RECEIVE_MESSAGE', (data)=>{ //This is printing multiple times
    console.log('RECIEVED FROM SERVER',data)
  });

  useEffect(() => {
    if(isActive){
        io.emit('SEND_TIME',{
            time:`${formatDate(hours)}:${formatDate(minutes)}:${formatDate(seconds)}`
        })
    }
  });

  const formatDate = t => (t <=9)?`0${t}`:t;

  const handleStart = () => {
    setIsActive(!isActive);
    if (isActive) {
      pause();
    } else {
      start();
    }
  };

  const handleReset = () => {
    reset();
    setIsActive(false);
  };

  return (
    <div style={{ textAlign: "center" }}>
      <div style={{ fontSize: "100px" }}>
        <span>{formatDate(hours)}</span>:<span>{formatDate(minutes)}</span>:
        <span>{formatDate(seconds)}</span>
      </div>
      <button onClick={handleStart}>{isActive ? "Stop" : "Start"}</button>
      <button onClick={handleReset}>Reset</button>
    </div>
  );
}

If I add the event handler (io.on('RECEIVE MESSAGE')) on app.js instead of the Timer component it works correctly (printing just one time each second)

I think the issue is related to the react useEffect hook and render behavior

Thanks!

Upvotes: 2

Views: 3808

Answers (1)

Rohan T
Rohan T

Reputation: 374

When using useEffect(), pass [isActive] array to second argument of useEffect() method.

useEffect(() => {
    if(isActive){
        io.emit('SEND_TIME',{
            time:`${formatDate(hours)}:${formatDate(minutes)}:${formatDate(seconds)}`
        })
    }
  },[isActive]);

When the page renders, useEffect() also runs that many times. But if you pass [isActive] in useEffect() second argument then useEffect() will keep track of isActive, if it changes then only useEffect() will run.

Read useEffect API docs

Upvotes: 1

Related Questions