Ad

Socket.io In Deeply Nested Express Router

- 1 answer

I am trying to pass a socket.io instance into Express Routes. Currently I am setting up the socket.io server as shown on their documentation. To pass the object into the routes I am using app.set('io', io) and then in the route const io = req.app.get('io') within the route. After getting the io object, I am simply emitting to all listening users. This works for the first request but then the second time the same request is made, two events get sent to every client. The third request emits the data three times and so on.

// App.js file
//  Server imports
const app = require('express')(),
    http = require('http').Server(app),
    io = require('socket.io')(http),
    session = require('express-session');

//  Add socket.io to req for use in latter requests
app.set('io', io);

io.on('connection', (socket) => {
    console.log('User has connected to socket.io');

    socket.on('disconnect', () => {
        console.log('User has disconnected');
    });
});

//  Route Setups
app.use('/api', require('./routes'));

http.listen(process.env.PORT, () => {
    console.log(`Server running on port: ${process.env.PORT}`);
});
// Route  index.js
const router = require('express').Router();

router.use('/project', require('./project/project'));

module.exports = router;
// Project Routes
const router = require('express').Router(),
    Project = require('../../models/project');

// Route for creating new Projects
router.post('/', (req, res, next) => {
    Project.createProject(req.body.item, (err, doc) => {
        if(err) return next(err);
        res.json({_id: doc._id, projectName: doc.projectName});

        const io = req.app.get('io');
        io.emit('project', {project: {
            _id: doc._id, 
            client: doc.client,
            jobId: doc.jobId,
            projectName: doc.projectName,
            eventDate: doc.eventDate,
            dateModified: doc.dateModified,
            eventLogo: doc.eventLogo
        }});
    })
});
// index.js
// Import React Standard components
// Import and setup BrowserRouter

import App from './components/App';

import io from 'socket.io-client';
const socket = io('http://192.168.1.2:8080');


ReactDOM.render(
    <BrowserRouter>
        <App socket={socket}/>
    </BrowserRouter>
, document.getElementById('root'));


// Front End React File for displaying projects - App.js
// I cut down on some of the code to keep it simple.
import React, { Component } from 'react';

import NewProject from './NewProject';

export default class Projects extends Component {
  state = {
    projects: []
    newProject: false
  }

  closeWindow = () => {
    // Sets the state for new Project to true or false to show or hide the component 
  }

  componentDidMount = () => {
    // Fires mutiple times for each request made to update project or create new project
    this.props.socket.on('project', ({project, index}) => {
      console.log("DATA RECIEVED", project, index);
      const projects = this.state.projects;

      // If a project and index is sent, update an excising project
      if(project !== undefined && index !== undefined) {
         console.log('Project to ypdate: ',projects[index])
         projects[index] = Object.keys(projects[index]).reduce((acc, key) => {
            // Update all keys passed down from the server
         }, {});
      // If just a project is sent, add a new project
      } else if(project !== undefined && index === undefined) {
                projects.push(project);
      // If just an index is sent, remove the project
      } else if(index !== undefined) {
        const projectIndex = this.props.projects.findIndex((item) => {
          if(item._id === index) return true;
            return null;
          });
          projects.splice(projectIndex, 1);
        }
      return this.setState({projects});
    });
  }

  render() {
    return [
      this.state.newProject && <NewProject key="0" closeWindow={this.closeWindow} />
      <main key="1">
        // Display the projects
        <button type="button" onClick={this.closeWindow}>New Project</button>
      </main>
    ]
}
// Front End React File for Creating New Project
// NewProject.js
import React, { Component } from 'react';

export default class CreateProject extends Component {
  state = {
    client: '',
    projectName: '',
    jobId: '',
    eventDate: '',
    eventLogo: '',
    completeForm: false
  }

  createProject = e => {
    if(!this.state.completeForm) return;
    fetch('/api/asset/project', {
      method: 'POST',
      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify(this.state)
    })
    .then(res => res.json())
    .then(res => {
       return this.props.closeWindow();
    })
    .catch(error => {// Handle Error})
  }

  render() {
    return (
      <div className="popup">
        <form onSubmit={this.createProject}>
          // All inputs for form.
        </form>
      </div>
    )
  }
}

I was expecting each user to revive the data emitted once but depending on how many times the request is made, they get the data that many times.

Thank you so much!!

Ad

Answer

What worked for me was adding this to the Component Will Unmount

componentWillUnmount = () => {
   this.props.socket.off('project');
}

I did not realize the event listener was not detaching itself when the component unmounted.

Ad
source: stackoverflow.com
Ad