Issue with Socket.io not updating content dynamically

I am trying to use web sockets in my queue management app. There are issues with updating the data in real time. Often, I need to refresh to get the update or refresh to use different functionality again.

Ignore a lot of other red flags (i.e. I’m currently storing classroom data locally in an object instead of a server).

server.js

const express = require("express");
const app = express();
const http = require("http");
const socketIo = require("socket.io");
const server = http.createServer(app);
const io = socketIo(server, {
  cors: {
    origin: "http://localhost:3001",
    methods: ["GET", "POST"],
    credentials: true,
  },
  pingTimeout: 18000000,
});
const cors = require("cors");
const path = require("path");
const uuid = require("uuid");
const fs = require("fs");

app.use(cors());
app.use(express.json());

const dataFilePath = path.join(__dirname, "classrooms.json");
let classrooms = {};

if (fs.existsSync(dataFilePath)) {
  const data = fs.readFileSync(dataFilePath, "utf8");
  classrooms = JSON.parse(data);
}

function saveClassroomsToFile() {
  fs.writeFileSync(dataFilePath, JSON.stringify(classrooms), "utf8");
}

io.on("connection", (socket) => {
  socket.on("joinClassroom", (classroomId) => {
    socket.join(classroomId);

    // Send initial data to the client
    socket.emit("initialData", {
      queue: classrooms[classroomId].queue,
      attended: classrooms[classroomId].attended,
      selectedStudent: classrooms[classroomId].selectedStudent,
      participation: classrooms[classroomId].participation,
      name: classrooms[classroomId].name, // CHANGED
    });
  });

  socket.on("raiseHand", ({ studentId, classroomId, studentName }) => {
    if (
      classrooms[classroomId] &&
      !classrooms[classroomId].queue.some((student) => student.id === studentId)
    ) {
      classrooms[classroomId].queue.push({ id: studentId, name: studentName });
      io.to(classroomId).emit("queueUpdate", classrooms[classroomId].queue);
    }
  });

  socket.on("lowerHand", ({ studentId, classroomId }) => {
    if (classrooms[classroomId]) {
      classrooms[classroomId].queue = classrooms[classroomId].queue.filter(
        (student) => student.id !== studentId
      );
      io.to(classroomId).emit("queueUpdate", classrooms[classroomId].queue);
    }
  });

  socket.on("selectStudent", ({ classroomId, isColdCall }) => {
    if (!classrooms[classroomId]) {
      return;
    }

    let selectedStudent;
    if (isColdCall) {
      const attendedStudents = classrooms[classroomId].attended;
      if (attendedStudents.length === 0) {
        return;
      }
      const randomIndex = Math.floor(Math.random() * attendedStudents.length);
      selectedStudent = attendedStudents[randomIndex];
    } else {
      if (classrooms[classroomId].queue.length === 0) {
        return;
      }
      const sortedQueue = [...classrooms[classroomId].queue].sort(
        (a, b) =>
          (classrooms[classroomId].participation[a.id] || 0) -
          (classrooms[classroomId].participation[b.id] || 0)
      );
      selectedStudent = sortedQueue[0];
      classrooms[classroomId].queue = classrooms[classroomId].queue.filter(
        (student) => student.id !== selectedStudent.id
      );
    }

    classrooms[classroomId].selectedStudent = selectedStudent;
    classrooms[classroomId].participation[selectedStudent.id] =
      (classrooms[classroomId].participation[selectedStudent.id] || 0) + 1;
    saveClassroomsToFile();

    io.to(classroomId).emit("studentSelected", selectedStudent);
    io.to(classroomId).emit("queueUpdate", classrooms[classroomId].queue);
    io.to(classroomId).emit("participationUpdate", {
      student: selectedStudent,
      count: classrooms[classroomId].participation[selectedStudent.id],
    });
  });

  socket.on("resetQueue", ({ classroomId }) => {
    if (classrooms[classroomId]) {
      classrooms[classroomId].queue = [];
      classrooms[classroomId].selectedStudent = null;
      io.to(classroomId).emit("queueUpdate", []);
      io.to(classroomId).emit("clearSelectedStudent");
    }
  });
});

app.post("/create-classroom", (req, res) => {
  const classroomId = uuid.v4().slice(0, 6);
  const { name } = req.body; // CHANGED
  classrooms[classroomId] = {
    name: name, // CHANGED
    queue: [],
    participation: {},
    attended: [],
    selectedStudent: null,
  };
  saveClassroomsToFile();
  res.status(200).json({ classroomId });
});

app.post("/log-attendance", (req, res) => {
  const { classroomId, studentName, studentId } = req.body;
  if (!classrooms[classroomId]) {
    return res.status(404).json({ message: "Classroom not found" });
  }
  if (
    !classrooms[classroomId].attended.some(
      (student) => student.id === studentId
    )
  ) {
    classrooms[classroomId].attended.push({ id: studentId, name: studentName });
    saveClassroomsToFile();
    res.status(200).json({ message: "Attendance logged" });
    io.to(classroomId).emit(
      "attendanceUpdate",
      classrooms[classroomId].attended
    );
  } else {
    res.status(200).json({ message: "Student already logged" });
  }
});

app.post("/verify-classroom", (req, res) => {
  const { classroomId } = req.body;
  if (!classrooms[classroomId]) {
    return res.status(404).json({ message: "Classroom not found" });
  }
  const classroomName = classrooms[classroomId].name;
  res
    .status(200)
    .json({ message: "Classroom found", classroomName: classroomName }); // CHANGED
});

app.use(express.static(path.join(__dirname, "frontend/public")));

app.get("*", (req, res) => {
  res.sendFile(path.join(__dirname, "frontend/public", "index.html"));
});

server.listen(3000, () => {
  console.log("Server listening on port 3000");
});

Classroom.js

import React, { useEffect, useState } from "react";
import io from "socket.io-client";
import { useParams } from "react-router-dom";
import "./Classroom.css";
import { useLocation } from "react-router-dom";

const socket = io("http://localhost:3000");

const Classroom = () => {
  const { classroomId } = useParams();

  const [queue, setQueue] = useState([]);
  const [selectedStudent, setSelectedStudent] = useState(null);
  const [attendanceList, setAttendanceList] = useState([]);
  const location = useLocation(); // CHANGED
  const [classroomName, setClassroomName] = useState(
    location.state?.classroomName || ""
  ); // CHANGED

  useEffect(() => {
    socket.emit("joinClassroom", classroomId);

    socket.on("initialData", (data) => {
      setQueue(data.queue);
      setAttendanceList(data.attended);
      setSelectedStudent(data.selectedStudent);
      setClassroomName(data.name); // CHANGED
    });

    socket.on("queueUpdate", (newQueue) => {
      console.log("Queue update received:", newQueue);
      setQueue(newQueue);
    });

    socket.on("attendanceUpdate", (newAttendance) => {
      console.log("Attendance update received:", newAttendance);
      setAttendanceList(newAttendance);
    });

    socket.on("studentSelected", (newSelectedStudent) => {
      console.log("Student selected:", newSelectedStudent);
      setSelectedStudent(newSelectedStudent);
    });

    socket.on("clearSelectedStudent", () => {
      setSelectedStudent(null);
    });

    return () => {
      socket.off("initialData");
      socket.off("queueUpdate");
      socket.off("attendanceUpdate");
      socket.off("studentSelected");
      socket.off("clearSelectedStudent");
    };
  }, [classroomId]);

  const selectStudent = (isColdCall = false) => {
    socket.emit("selectStudent", { classroomId, isColdCall });
  };

  const resetQueue = () => {
    socket.emit("resetQueue", { classroomId });
    setSelectedStudent(null);
  };

  return (
    <>
      <nav className="nav-bar">
        <div>
          <span className="nav-title">{classroomName}</span>
          <span className="classroom-id">ID: {classroomId}</span>
        </div>
      </nav>
      <div className="container">
        <div className="attendance-stats">
          <div className="attendance-list">
            {attendanceList
              .slice(-20)
              .reverse()
              .map((student, index) => (
                <span key={index}>{student.name}</span>
              ))}
          </div>
          <div className="class-stats">
            <span>Total Students: {attendanceList.length}</span>
          </div>
        </div>
        <div className="main-content">
          <div className="card">
            <h2>Queue</h2>
            <div className="queue-container">
              <ul className="queue-list">
                {queue.map((student, index) => (
                  <li key={index} className="fade-in">
                    <span className="student-icon">{student.name[0]}</span>
                    {student.name}
                  </li>
                ))}
              </ul>
              <div className="button-container">
                <button onClick={() => selectStudent(false)}>Select</button>
                <button onClick={() => selectStudent(true)}>Cold Call</button>
                <button onClick={resetQueue}>Reset</button>
              </div>
            </div>
          </div>
          <div className="card">
            <h2>Selected Student</h2>
            {selectedStudent ? (
              <div className="selected-student slide-in">
                <div className="selected-student-icon">
                  {selectedStudent.name[0]}
                </div>
                <div className="selected-student-name">
                  {selectedStudent.name}
                </div>
              </div>
            ) : (
              <p>No student selected</p>
            )}
          </div>
        </div>
      </div>
    </>
  );
};

export default Classroom;

Student.js

import React, { useState, useEffect } from "react";
import { useParams, useLocation } from "react-router-dom";
import io from "socket.io-client";
import "./Student.css";

const socket = io("http://localhost:3000");

const Student = () => {
  const { classroomId } = useParams();
  const location = useLocation();
  const { studentName, studentId, classroomName } = location.state;

  const [participationCount, setParticipationCount] = useState(0);
  const [isHandRaised, setIsHandRaised] = useState(false);
  const [isSelected, setIsSelected] = useState(false);

  useEffect(() => {
    console.log("Joining classroom:", classroomId);
    socket.emit("joinClassroom", classroomId);

    socket.on("initialData", (data) => {
      setIsHandRaised(data.queue.some((student) => student.id === studentId));
      setParticipationCount(data.participation[studentId] || 0);
    });

    socket.on("participationUpdate", ({ student, count }) => {
      console.log("Participation update received:", student, count);
      if (student.id === studentId) {
        setParticipationCount(count);
      }
    });

    socket.on("queueUpdate", (newQueue) => {
      console.log("Queue update received:", newQueue);
      const handRaised = newQueue.some((student) => student.id === studentId);
      console.log("Is hand raised?", handRaised);
      setIsHandRaised(handRaised);
    });

    socket.on("studentSelected", (selectedStudent) => {
      if (selectedStudent.id === studentId) {
        setIsHandRaised(false);
        setIsSelected(true);
        console.log("You've been selected!");
      }
    });

    socket.on("clearSelectedStudent", () => {
      setIsSelected(false);
    });

    return () => {
      socket.off("initialData");
      socket.off("participationUpdate");
      socket.off("queueUpdate");
      socket.off("studentSelected");
      socket.off("clearSelectedStudent");
    };
  }, [classroomId, studentId]);

  const raiseHand = () => {
    socket.emit("raiseHand", { studentId, classroomId, studentName });
  };

  const lowerHand = () => {
    socket.emit("lowerHand", { studentId, classroomId });
  };

  const closeModal = () => {
    setIsSelected(false);
  };

  return (
    <div className="student-container">
      <nav className="student-nav-bar">
        <span className="student-nav-title">{classroomName}</span>
        <span className="student-classroom-id">Class ID: {classroomId}</span>
      </nav>

      <div className="student-info-card">
        <div className="student-avatar">{studentName[0]}</div>
        <h1 className="student-name">{studentName}</h1>
        <p className="student-participation-count">
          Times Participated: {participationCount}
        </p>
        <button
          className="student-hand-button"
          onClick={isHandRaised ? lowerHand : raiseHand}
        >
          {isHandRaised ? "Lower Hand" : "Raise Hand"}
        </button>
      </div>

      {isSelected && (
        <div className="student-selected-modal">
          <div className="student-selected-modal-content">
            <button className="close-modal-button" onClick={closeModal}>
              ×
            </button>
            <h2>You've been selected!</h2>
            <p>It's your turn to participate.</p>
          </div>
        </div>
      )}
    </div>
  );
};

export default Student;

HomePage.js

import React, { useState } from "react";
import { useNavigate } from "react-router-dom";
import axios from "axios";
import Modal from "react-modal";
import "./HomePage.css";

Modal.setAppElement("#root");

const HomePage = () => {
  const navigate = useNavigate();
  const [modalIsOpen, setModalIsOpen] = useState(false);
  const [joinClassroomId, setJoinClassroomId] = useState("");
  const [studentName, setStudentName] = useState("");
  const [studentId, setStudentId] = useState("");
  const [errorMessage, setErrorMessage] = useState("");

  // New state variables for sign-in and registration
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [isSignIn, setIsSignIn] = useState(true);
  const [showSignInModal, setShowSignInModal] = useState(false);
  const [classroomName, setClassroomName] = useState("");
  const [showClassroomNameInput, setShowClassroomNameInput] = useState(false);

  const createClassroom = async () => {
    setShowSignInModal(true);
  };

  const handleSignIn = async (e) => {
    e.preventDefault();
    
    // FUTURE VALIDATION HERE against database
    if (email && password) {
      setShowSignInModal(false);
      setShowClassroomNameInput(true);
    } else {
      setErrorMessage("Please enter both email and password");
    }
  };

  const handleRegistration = async (e) => {
    e.preventDefault();
    // FUTURE SEND DATA TO BACKEND HERE
    if (email && password) {
      // Store credentials in local storage (Note: This is not secure for production)
      localStorage.setItem(
        "userCredentials",
        JSON.stringify({ email, password })
      );
      setShowSignInModal(false);
      setShowClassroomNameInput(true);
    } else {
      setErrorMessage("Please enter both email and password");
    }
  };

  const handleClassroomNameSubmit = async (e) => {
    e.preventDefault();
    try {
      const response = await axios.post(
        "http://localhost:3000/create-classroom",
        { name: classroomName }
      );
      navigate(`/classroom/${response.data.classroomId}`, {
        state: { classroomName },
      });
    } catch (error) {
      console.error("Error creating classroom:", error);
      setErrorMessage("Failed to create classroom. Please try again.");
    }
  };

  const openModal = () => setModalIsOpen(true);
  const closeModal = () => {
    setModalIsOpen(false);
    setErrorMessage("");
  };

  const joinClassroom = async () => {
    if (!joinClassroomId || !studentName || !studentId) {
      setErrorMessage("Please fill in all fields");
      return;
    }

    try {
      const response = await axios.post(
        "http://localhost:3000/verify-classroom",
        {
          classroomId: joinClassroomId,
        }
      );
      const { classroomName } = response.data;
      await axios.post("http://localhost:3000/log-attendance", {
        classroomId: joinClassroomId,
        studentName,
        studentId,
      });
      navigate(`/student/${joinClassroomId}`, {
        state: { studentName, studentId, classroomName },
      });
      closeModal();
    } catch (error) {
      setErrorMessage("Classroom ID not found. Please enter a valid ID.");
    }
  };

  return (
    <div className="homepage-container">
      <h1 className="homepage-title">Welcome to the Classroom App</h1>
      <p className="homepage-subtitle">Choose an option to get started:</p>
      <div className="options-container">
        <div className="option-card" onClick={createClassroom}>
          <div className="option-icon">🏫</div>
          <h2 className="option-title">Create Classroom</h2>
          <p className="option-description">
            Start a new virtual classroom for your students
          </p>
          <button className="option-button create-button">Create Now</button>
        </div>
        <div className="option-card" onClick={openModal}>
          <div className="option-icon">🚪</div>
          <h2 className="option-title">Join Classroom</h2>
          <p className="option-description">
            Enter an existing classroom with your details
          </p>
          <button className="option-button join-button">Join Now</button>
        </div>
      </div>

      <Modal
        isOpen={modalIsOpen}
        onRequestClose={closeModal}
        contentLabel="Join Classroom Modal"
        className="modal"
        overlayClassName="modal-overlay"
      >
        <h2 className="modal-title">Join Classroom</h2>
        <input
          type="text"
          value={joinClassroomId}
          onChange={(e) => setJoinClassroomId(e.target.value)}
          placeholder="Enter Classroom ID"
          className="modal-input"
        />
        <input
          type="text"
          value={studentName}
          onChange={(e) => setStudentName(e.target.value)}
          placeholder="Enter your Name"
          className="modal-input"
        />
        <input
          type="text"
          value={studentId}
          onChange={(e) => setStudentId(e.target.value)}
          placeholder="Enter your Student ID"
          className="modal-input"
        />
        {errorMessage && <p className="error-message">{errorMessage}</p>}
        <div className="modal-button-group">
          <button className="create-button" onClick={joinClassroom}>
            Join
          </button>
          <button className="join-button" onClick={closeModal}>
            Cancel
          </button>
        </div>
      </Modal>

      <Modal
        isOpen={showSignInModal}
        onRequestClose={() => setShowSignInModal(false)}
        contentLabel="Sign In Modal"
        className="modal"
        overlayClassName="modal-overlay"
      >
        <h2 className="modal-title">
          {isSignIn ? "Sign In" : "Create Account"}
        </h2>
        <form onSubmit={isSignIn ? handleSignIn : handleRegistration}>
          <input
            type="email"
            value={email}
            onChange={(e) => setEmail(e.target.value)}
            placeholder="Email"
            className="modal-input"
            required
          />
          <input
            type="password"
            value={password}
            onChange={(e) => setPassword(e.target.value)}
            placeholder="Password"
            className="modal-input"
            required
          />
          {errorMessage && <p className="error-message">{errorMessage}</p>}
          <div className="modal-button-group">
            <button type="submit" className="create-button">
              {isSignIn ? "Sign In" : "Create Account"}
            </button>
            <button
              type="button"
              className="join-button"
              onClick={() => setIsSignIn(!isSignIn)}
            >
              {isSignIn
                ? "Need an account? Sign Up"
                : "Already have an account? Sign In"}
            </button>
          </div>
        </form>
      </Modal>

      <Modal
        isOpen={showClassroomNameInput}
        onRequestClose={() => setShowClassroomNameInput(false)}
        contentLabel="Classroom Name Input"
        className="modal"
        overlayClassName="modal-overlay"
      >
        <h2 className="modal-title">Create Classroom</h2>
        <form onSubmit={handleClassroomNameSubmit}>
          <input
            type="text"
            value={classroomName}
            onChange={(e) => setClassroomName(e.target.value)}
            placeholder="Enter Classroom Name"
            className="modal-input"
            required
          />
          <div className="modal-button-group">
            <button type="submit" className="create-button">
              Create
            </button>
            <button
              type="button"
              className="join-button"
              onClick={() => setShowClassroomNameInput(false)}
            >
              Cancel
            </button>
          </div>
        </form>
      </Modal>
    </div>
  );
};

export default HomePage;

I have tried a lot.

New contributor

Ethan O’Brien is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.

1

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật