'MERN Application - Problem with notifications and mapping array

I'm on a MERN stack to create a chat application. And when I'm trying to display notifications when there is a new message in another chatroom or a new PM I'm having this issue :

Sidebar.js:99 Uncaught TypeError: Cannot read properties of undefined (reading 'Tech') at Sidebar.js:99:1 at Array.map (<anonymous>)

Tech is a room in an array, server side. And problem is on front side :

<ul className='chatroom'>
          {rooms.map((room, idx) => (
            <li 
              key={idx}
              onClick={() => joinRoom(room)}
              className={room === currentRoom ? 'actif' : ''}
            > 
              {room} 
              {currentRoom !== room &&   
                <span className='notifs'>
                  {user.newMessages[room]}
                </span>
              }
            </li>
          ))}
        </ul>

on this part particulary :

{currentRoom !== room &&   
        <span className='notifs'>
           {user.newMessages[room]}
         </span>

I tried a lot of things, starting by testing if the state is here with a ternary or a conditional. But nothing more. And it works because the first part with {room} is working great, all rooms displays on a list.

I'm on it since three days, I'm losing all my hairs 🤣 so if you have any idea...

I'm putting parts of the code if it can help

Sidebar.js

import React, { useContext, useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { AppContext } from '../../context/appContext';
import { Div } from './Sidebar.elements'
import { addNotifications, resetNotifications } from '../../features/userSlice'
import { FaCircle } from 'react-icons/fa'

function Sidebar() {

  const user = useSelector((state) => state.user);
  const dispatch = useDispatch();
  const { 
    socket, 
    setMembers, 
    members, 
    setCurrentRoom, 
    setRooms, 
    privateMemberMsg, 
    rooms, 
    setPrivateMemberMsg, 
    currentRoom 
  } = useContext(AppContext)

  function joinRoom(room, isPublic = true){
    if(!user){
      return alert("Veuillez vous identifier");
    }

    socket.emit("join-room", room);
    setCurrentRoom(room);

    if (isPublic){
      setPrivateMemberMsg(null);
    }

    // dispatch for notifications
    dispatch(resetNotifications(room));

  }

  socket.off("notifications").on("notifications", (room) => {
    if(currentRoom !== room) dispatch(addNotifications(room));
    console.log(room);
    
  });

 function getRooms(){
    fetch("http://localhost:5001/rooms")
    .then((res) => res.json())
    .then((data) => setRooms(data));
  }

  useEffect(()=>{
    if(user){
      setCurrentRoom('General');
      getRooms();
      socket.emit('join-room', 'General');
      socket.emit('new-user');
    }
  },[])

  socket.off('new-user').on('new-user', (payload) => {
    setMembers(payload);
  })

 

  function orderIds(id1, id2) {
    if(id1 > id2) {
      return id1 + "-" + id2;
    } else {
      return id2 + "-" + id1;
    }
  }

  function handlePrivateMemberMsg(member){
    setPrivateMemberMsg(member);
    const roomId = orderIds(user._id, member._id);
    joinRoom(roomId, false);
  }
  
  if(!user){
    return <></>;
  }

  return (
    <>
      <Div>
        <h2>Chatrooms disponibles</h2>
        <ul className='chatroom'>
          {rooms.map((room, idx) => (
            <li 
              key={idx}
              onClick={() => joinRoom(room)}
              className={room === currentRoom ? 'actif' : ''}
            > 
              {room} 
              {currentRoom !== room &&   
                <span className='notifs'>
                  {user.newMessages[room]}
                </span>
              }
            </li>
          ))}
        </ul>

        <h2 className='chat'>Chaters</h2>
        <ul className='membre'>
          {members.map((member) => (
              <li 
                key={member.id}
                className={`
                  ${privateMemberMsg?._id === member?._id ? 'actif' : ''}
                  ${member._id === user._id ? 'disabling' : ''}
                `}
                onClick={() => handlePrivateMemberMsg(member)}
              >
                <div className="row">
                  <div className="col-2">
                    <img src={member.picture} className='member-pic'/>
                    {member.status == "En ligne" ? <FaCircle className='online'/> : <FaCircle className='offline'/>}
                  </div>
                  <div className="col-9">
                    {member.name}
                    {member._id == user?._id && " (Vous)"}
                    {member.status == "Hors Ligne" && <span className="offtxt">(Offline)</span> }
                  </div>
                  <div className="col-1">
                    <span className="notifs">
                      {/* {user.newMessages[orderIds(member._id, user._id)]} */}
                    </span>
                  </div>
                </div>
              </li>
          ))}
        </ul>
      </Div>
    </>
  )
}

export default Sidebar

appApi.js

import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'

// define a service user and base URL

const appApi = createApi({
    reducerPath: 'appApi',
    baseQuery: fetchBaseQuery({
        baseUrl: 'http://localhost:5001'
    }),

    endpoints: (builder) => ({
        // creating the user
        signupUser: builder.mutation({
            query: (user) => ({
                url: '/users',
                method: "POST",
                body: user,
            }),
        }),

        // login
        loginUser: builder.mutation({
            query: (user) => ({
                url:'/users/login',
                method: "POST",
                body: user,
            }),
        }),

        //logout
        logoutUser: builder.mutation({
            query: (payload) => ({
                url: '/logout',
                method: "DELETE",
                body: payload,
            }),
        }),
    }),
});

export const { useSignupUserMutation, useLoginUserMutation, useLogoutUserMutation } = appApi;

export default appApi;

appContext.js

import { io } from 'socket.io-client'
import React from 'react'

const SOCKET_URL = "http://localhost:5001";
export const socket = io(SOCKET_URL);
//app context

export const AppContext = React.createContext()

userSlice.js

import { createSlice } from "@reduxjs/toolkit";
import appApi from "../services/appApi";

export const userSlice = createSlice({
    name: "user",
    initialState: null,
    reducers: {
        addNotifications: (state, { payload }) => {
            if (state.newMessages[payload]) {
                state.newMessages[payload] = state.newMessages[payload] + 1;
            } else {
                state.newMessages[payload] = 1;
            }
            
        },
        resetNotifications: (state, { payload }) => {
            delete state.newMessages[payload];
        },
    },

    extraReducers: (builder) => {

        // save user after signup
        builder.addMatcher(appApi.endpoints.signupUser.matchFulfilled, (state, { payload }) => payload);

        // save user after login
        builder.addMatcher(appApi.endpoints.loginUser.matchFulfilled, (state, { payload }) => payload);

        // logout : destroy user session
        builder.addMatcher(appApi.endpoints.logoutUser.matchFulfilled, () => null);
    },
})

export const { addNotifications, resetNotifications } = userSlice.actions;
export default userSlice.reducer;

server.js on back side

const express = require('express');
const app = express();
const userRoutes = require('./routes/userRoutes');

const User = require('./models/User');
const Message = require('./models/Message');

const rooms = [
    'General',
    'Tech',
    'Crypto',
    'Gaming'
];
const cors = require('cors');


app.use(express.urlencoded({extended: true}));
app.use(express.json());
app.use(cors());

app.use('/users', userRoutes)
require('./connection')

const server = require('http').createServer(app);
const PORT = 5001;
const io = require('socket.io')(server, {
    cors: {
        origin: 'http://localhost:3000',
        methods: ['GET', 'POST']
    }
})


app.get('/rooms', (req, res)=> {
    res.json(rooms)
})


async function getLastMessagesFromRoom(room){
    let roomMessages = await Message.aggregate([
        {$match: {to: room}},
        {$group: {_id: '$date', messagesByDate: {$push: '$$ROOT'}}}
    ])
    return roomMessages;
}

function sortRoomMessagesByDate(messages){
    return messages.sort(function(a, b){
        let date1 = a._id.split('/');
        let date2 = b._id.split('/');

        date1 = date1[2] + date1[0] + date1[1];
        date2 = date2[2] + date2[0] + date2[1];

        return date1 < date2 ? -1 : 1
    })
}

// socket connection

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


    socket.on('new-user', async ()=>{
        const members = await User.find();
        io.emit('new-user', members);
    })

    socket.on('join-room', async(room)=> {
        socket.join(room);
        let roomMessages = await getLastMessagesFromRoom(room);
        roomMessages = sortRoomMessagesByDate(roomMessages);
        socket.emit('room-messages', roomMessages);
    })

    socket.on('message-room', async(room, content, sender, time, date) => {
        const newMessage = await Message.create({content, from: sender, time, date, to: room});
        let roomMessages = await getLastMessagesFromRoom(room);
        roomMessages = sortRoomMessagesByDate(roomMessages);

        // sending message to room
        io.to(room).emit('room-messages', roomMessages);

        // And send notification when sending new message
        socket.broadcast.emit('notifications', room)
    })

    app.delete('/logout', async(req, res)=>{
        try {
            const {_id, newMessages} = req.body;
            const user = await User.findById(_id);
            user.status = "Hors Ligne";
            user.newMessages = newMessages;
            await user.save();
            const members = await User.find();
            socket.broadcast.emit('new-user', members);
            res.status(200).send();
        } catch (e) {
            console.log(e);
            res.status(400).send();
        }
    })

})

server.listen(PORT, () => {
    console.log('listening to port ', PORT);
})

Edit

Adding the App.js that I forgot

App.js

import './App.css';
import { 
  BrowserRouter as Router, 
  Route, 
  Routes 
} from 'react-router-dom'
import Home from './pages/Home';
import Login from './pages/Login';
import Signup from './pages/Signup';
import Chat from './pages/Chat';
import GlobalStyle from './GlobalStyles';
import { useSelector } from 'react-redux';
import { useState } from 'react';
import { AppContext, socket } from './context/appContext'

function App() {
  const [rooms, setRooms] = useState([]);
  const [currentRoom, setCurrentRoom] = useState([]);
  const [members, setMembers] = useState([]);
  const [messages, setMessages] = useState([]);
  const [privateMemberMsg, setPrivateMemberMsg] = useState({});
  const [newMessages, setNewMessages] = useState({});
  const user = useSelector((state) => state.user);
  return (
    <AppContext.Provider value={{ 
      socket, 
      currentRoom, 
      setCurrentRoom, 
      members, 
      setMembers, 
      messages, 
      setMessages, 
      privateMemberMsg, 
      setPrivateMemberMsg, 
      newMessages, 
      setNewMessages, 
      rooms, 
      setRooms 
    }} >
      <Router>
        <GlobalStyle/>
        <Routes>
          <Route path="/" element={<Home/>} />

          {!user && (
            <>
              <Route path="/login" element={<Login/>} />
              <Route path="/signup" element={<Signup/>} />
            </>
          )}
          <Route path="/chat" element={<Chat/>} />
        </Routes>
      </Router>
   </AppContext.Provider>
  );
}

export default App;


Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source