'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 |
---|