Search code examples
node.jsreactjsexpressreduxsocket.io

How to use socketio inside controllers?


I have a application created with Reactjs,Redux,Nodejs,MongoDB. I have created socketio in backend side.

server.js


const express = require('express');
const mongoose = require('mongoose');
const cors = require('cors');
const app = express();

const routes = require('./routes/api/routes');
var server = require('http').Server(app);
const io = require('./socket').init(server);
app.use(express.json());

require('dotenv').config();
mongoose.connect(process.env.MONGO_DB).then(console.log('connected'));
const corsOptions = {
credentials: true, //access-control-allow-credentials:true
optionSuccessStatus: 200,
};

app.use(cors(corsOptions));

app.use(routes);

if (
process.env.NODE_ENV === 'production' ||
process.env.NODE_ENV === 'staging'
) {
// Set static folder
app.use(express.static('client/build'));

app.get('*', (req, res) => {
 res.sendFile(path.resolve(__dirname, 'client', 'build', 'index.html'));
});
}

io.on('connection', (socket) => {
console.log('New client connected');

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

server.listen(process.env.PORT || 5000, () => {
console.log('socket.io server started on port 5000');
});

socket.js

let io;

module.exports = {
  init: (httpServer) => {
    return (io = require('socket.io')(httpServer, {
      cors: {
        origin: 'http://localhost:3000',
        methods: ['GET', 'POST'],
      },
    }));
  },

  getIO: () => {
    if (!io) {
      throw new Error('Socket.io is not initialized');
    }
    return io;
  },
};

In controller.js I am creating new item to inside mongodb and also using socketio. I am emitting new Item that I created. It looks like that

controller.js


const createTeam = async (req, res) => {
  const userId = req.user.id;
  const newItem = new Item({
    name: req.body.name,
    owner: userId,
  });
  await newItem.save((err, data) => {
    if (err) {
      console.log(err);
      return res.status(500).json({
        message: 'Server Error',
      });
    }
  });
  await newItem.populate({
    path: 'owner',
    model: User,
    select: 'name',
  });

  await User.findByIdAndUpdate(
    userId,
    { $push: { teams: newItem } },
    { new: true }
  );

  io.getIO().emit('team', {
    action: 'creating',
    team: newItem,
  });

  res.json(newItem);
};

In frontend side I am listening the socketio server with socket.io-client. In my App.js I can see data that come from backend side when I console.log(data). My app working perfect until this stage. I can take the data from socketio, I can see the new client that connected. But when I send the data with dispatch, I app start to add infinite new items to database. Take a look at my App.js

import './App.css';
import 'bootstrap/dist/css/bootstrap.min.css';
import { AppNavbar } from './components/AppNavbar';
import { TeamList } from './components/TeamList';
import { loadUser } from './Store/Actions/AuthActions';
import { useEffect } from 'react';
import { useDispatch } from 'react-redux';
import { io } from 'socket.io-client';
import { addItems } from './Store/Actions/itemActions';

function App() {
  const dispatch = useDispatch();

  useEffect(() => {
    dispatch(loadUser());
    const socket = io('http://localhost:5000');
    socket.on('team', (data) => {
      if (data.action === 'creating') {
        dispatch(addItems(data.team));
      }
      // console.log(data);
    });
  }, []);

  return (
    <div className="App">
      <AppNavbar />
      <TeamList />
    </div>
  );
}

export default App;

My itemAction in redux side is also like that

export const addItems = (input) => (dispatch, getState) => {
  axios
    .post('/api/items/createTeam', input, tokenConfig(getState))
    .then((res) =>
      dispatch({
        type: ADD_ITEM,
        payload: res.data,
      })
    )
    .catch((err) =>
      dispatch(
        returnErrors(err.response.data, err.response.status, 'GET_ERRORS')
      )
    );
};

My problem is how to stop infinite callling of api after implement socketio. How to stop infinite loop efficiently?


Solution

  • Infinite loop is steming from not dispatching "type: ADD_ITEM" once.This issue cause that your itemAction always would dispatch "type: ADD_ITEM" and payload with it when after every fetching then your react would re-render page again and again.

    You should get rid of your dispatching action inside of addItems function and dispatch your action only inside of useEffect in App.js file .

    Your code snippet should look like this in App.js:

    useEffect(() => {
        //...
        const socket = openSocket('http://localhost:5000');
        socket.on('postsChannel', (data) => {
          if (data.action === 'creating') {
            dispatch({ type: ADD_ITEM, payload: data.team });
          }
        });
    }, [dispatch]);