Conversation
mateacademy-ai-mentor
left a comment
There was a problem hiding this comment.
Review Summary
Great work on building this chat application! You've successfully implemented all the required features, and the overall structure is solid. However, I am requesting a few changes to fix some critical functionality bugs before this can be approved.
The most important issue is in server/index.js. Currently, new messages are broadcast to all users with io.emit(), regardless of which room they are in. For a multi-room chat, messages should only be sent to users within that specific room. You can fix this by having clients socket.join(roomName) and then using io.to(roomName).emit() on the server.
Additionally, there are a couple of UI logic bugs:
- In
App.jsx, when a user tries to delete the room they are in, the app switches them to 'General' even if they cancel the confirmation dialog. The room-switching logic should only execute after a successful deletion. - In
Sidebar.jsx, clicking the rename or delete buttons also triggers the action to join the room due to event bubbling. You can prevent this by callingevent.stopPropagation()in the buttons'onClickhandlers.
Once these functional issues are resolved, your project will be in great shape!
✨ Please rate this review in the Platform's notification center1. I will be grateful for your feedback! ✨
Important
Fix the issues, commit the changes, and then re-request my review using the Re-request review button2 in the Reviewers tab.
Tip
If you have any comments about this review, please leave a message in the Add a comment section below and mention me with the @mateacademy-ai-mentor tag3.
I will read your message during my next review of your pull request. Please do not reply directly to my comments, as that feature is not supported yet.
Caution
Do not close the pull request until the review is complete. Closing it early can lead to unexpected behavior and disrupt the review process.
Footnotes
|
|
||
| <div style={{ flex: 1, padding: '20px', overflowY: 'auto', backgroundColor: '#fafafa' }}> | ||
| {messages | ||
| .filter(msg => msg.room === currentRoom || msg.author === 'System') |
There was a problem hiding this comment.
The condition msg.author === 'System' causes the initial "Welcome to the chat!" message to appear in every room. According to the requirements, users should see messages specific to the room they join. Is this the intended behavior, or should this system message be scoped to a particular room like 'General'?
| {messages | ||
| .filter(msg => msg.room === currentRoom || msg.author === 'System') | ||
| .map((msg, i) => ( | ||
| <div key={i} style={{ marginBottom: '15px' }}> |
There was a problem hiding this comment.
Using the array index i as a key is not a stable identifier and is considered an anti-pattern in React. It can lead to performance issues and bugs with component state when the list is reordered or items are added/removed. It's better to use a unique identifier from the message object itself, such as a unique ID or a combination of timestamp and author.
| const [messages, setMessages] = useState([ | ||
| { author: 'System', text: 'Welcome to the chat!', time: new Date().toLocaleTimeString() } | ||
| ]); |
There was a problem hiding this comment.
This initial "Welcome" message will be immediately overwritten when the init_data event is received from the server, as the server's messages array is initially empty. This causes the welcome message to flash on the screen for a moment and then disappear. A better approach would be to add this message to the state after the initial data from the server has been loaded, or remove it if it's not a required feature.
client/src/App.jsx
Outdated
| if (currentRoom === roomToDelete) { | ||
| setCurrentRoom('General'); | ||
| } |
There was a problem hiding this comment.
This logic to switch the current room to 'General' is outside the window.confirm block. This means that if a user tries to delete the room they are currently in and then clicks "Cancel" on the confirmation dialog, they will still be switched to the 'General' room. This block should be moved inside the if (window.confirm(...)) block to ensure the room is only switched after the user confirms the deletion.
client/src/components/Sidebar.jsx
Outdated
|
|
||
| {room !== 'General' && ( | ||
| <div style={{ float: 'right', fontSize: '12px' }}> | ||
| <button onClick={() => onRenameRoom(room)}>✏️</button> |
There was a problem hiding this comment.
Clicking this button will also trigger the onClick handler of the parent <li> element due to event bubbling. This means that trying to rename a room will also cause the application to switch to that room. You should call event.stopPropagation() within this handler to prevent the event from bubbling up.
client/src/components/Sidebar.jsx
Outdated
| {room !== 'General' && ( | ||
| <div style={{ float: 'right', fontSize: '12px' }}> | ||
| <button onClick={() => onRenameRoom(room)}>✏️</button> | ||
| <button onClick={() => onDeleteRoom(room)}>❌</button> |
There was a problem hiding this comment.
Similar to the rename button, clicking the delete button will also trigger the onClick handler on the parent <li>. This can lead to unexpected behavior, especially if the room is deleted. You should use event.stopPropagation() here as well to fix this.
server/index.js
Outdated
|
|
||
| socket.on('send_message', (msg) => { | ||
| chatData.messages.push(msg); | ||
| io.emit('receive_message', msg); |
There was a problem hiding this comment.
Broadcasting with io.emit sends the message to every single connected client, regardless of which room they are currently in. This is inefficient as clients will receive messages for rooms they are not viewing. A better approach is to use Socket.IO's room feature. The server should have clients join a room and then you can emit messages specifically to that room using io.to(msg.room).emit('receive_message', msg). This ensures that only relevant clients receive the message.
mateacademy-ai-mentor
left a comment
There was a problem hiding this comment.
Review Summary
I am requesting changes because one core requirement is not yet met on the client–server interaction side. Specifically, your login flow never sends the chosen username to the server (e.g. via socket.emit('set_username', tempName) in client/src/App.jsx), so the server cannot actually receive or track the username, which violates the requirement that the user types a username and sends it to the server [CHECKLIST ITEM #2] and that this becomes the current username on the server side as well [CHECKLIST ITEM #3]. This is a functional gap rather than a cosmetic issue, since any server-side logic depending on usernames (joins, announcements, auditing) cannot work correctly.
Critical items to fix before this can pass:
- In
client/src/App.jsx, update your login handler so that after setting state/localStorage you also emit the username to the server, and then adjust thejoin_roomlogic so rooms are only joined after the username is set/emitted (or at least gatejoin_roomonusernamebeing truthy). - In
server/index.js, add a handler for that username event (e.g.socket.on('set_username', ...)) to associate the username with the socket (such assocket.username = name) so the server truly “has” the username.
Separately, you have several good pieces already in place: multi-room support, initial data loading for new users, and proper room-scoped broadcasting are all working well. After you wire the username from client to server and add a simple server handler, your implementation should satisfy the remaining core checklist items; the other comments (e.g., better React keys, message/system validation, room-rename/delete guards) are solid improvements but not strict blockers. Keep going—you’re very close to a fully robust solution.
✨ Please rate this review in the Platform's notification center1. I will be grateful for your feedback! ✨
Important
Fix the issues, commit the changes, and then re-request my review using the Re-request review button2 in the Reviewers tab.
Tip
If you have any comments about this review, please leave a message in the Add a comment section below and mention me with the @mateacademy-ai-mentor tag3.
I will read your message during my next review of your pull request. Please do not reply directly to my comments, as that feature is not supported yet.
Caution
Do not close the pull request until the review is complete. Closing it early can lead to unexpected behavior and disrupt the review process.
Footnotes
|
|
||
| <div style={{ flex: 1, padding: '20px', overflowY: 'auto', backgroundColor: '#fafafa' }}> | ||
| {messages | ||
| .filter(msg => msg.room === currentRoom || msg.author === 'System') |
There was a problem hiding this comment.
Filtering includes msg.author === 'System', which makes system messages appear in every room. If you intend system messages to be global this is fine, but if system messages should be room-specific consider giving them a room property or adjusting the filter.
| {messages | ||
| .filter(msg => msg.room === currentRoom || msg.author === 'System') | ||
| .map((msg, i) => ( | ||
| <div key={i} style={{ marginBottom: '15px' }}> |
There was a problem hiding this comment.
Using the array index i as the key can cause rendering issues when messages are added/removed. Prefer a stable unique identifier (e.g., msg.id or a combination of msg.time+msg.author) to avoid React list reconciliation problems.
| <span style={{ fontWeight: 'bold' }}>{msg.author}</span> | ||
| <span style={{ fontSize: '10px', color: '#888', marginLeft: '10px' }}>{msg.time}</span> | ||
| <p style={{ margin: '5px 0 0 0' }}>{msg.text}</p> |
There was a problem hiding this comment.
| localStorage.setItem('username', tempName); | ||
| setUserName(tempName); |
There was a problem hiding this comment.
The login handler saves the username to localStorage and updates state, but it does not send the username to the server. The task requires that the user types a username and sends it to the server (CHECKLIST #2) and that after sending it becomes the current username (CHECKLIST #3). Consider adding e.g. socket.emit('set_username', tempName) (or another agreed event) after setUserName(tempName), and optionally clear tempName.
client/src/App.jsx
Outdated
| useEffect(() => { | ||
| socket.emit('join_room', currentRoom); |
There was a problem hiding this comment.
Currently the effect that emits join_room runs regardless of login state, so a socket may join rooms before the user sends a username. To keep server room membership consistent and to avoid anonymous joins, guard this emit with the username (e.g. if (username) socket.emit('join_room', currentRoom)) or emit join_room from the login handler after emitting the username.
|
|
||
| socket.on('send_message', (msg) => { | ||
| chatData.messages.push(msg); | ||
| io.to(msg.room).emit('receive_message', msg); |
There was a problem hiding this comment.
Good: broadcasting to the specific room with io.to(msg.room).emit() is correct. As an extra check, verify msg.room is a valid room before emitting to avoid silent failures when a non-existent room name is provided.
| socket.on('create_room', (roomName) => { | ||
| if (!chatData.rooms.includes(roomName)) { | ||
| chatData.rooms.push(roomName); | ||
| io.emit('update_rooms', chatData.rooms); |
There was a problem hiding this comment.
In create_room you emit update_rooms, but in rename_room and delete_room you emit init_data. Make event usage consistent — either always emit update_rooms for room list changes or always send full init_data. Consistency simplifies client logic and avoids unnecessary full-state transfers.
| socket.on('delete_room', (roomName) => { | ||
| chatData.rooms = chatData.rooms.filter(room => room !== roomName); |
There was a problem hiding this comment.
Server currently allows deletion of any room. The client prevents deleting 'General', but the server should also enforce this to avoid trust issues. Add a guard to ignore/delete requests for 'General' (or validate request and send an error) before modifying chatData.
| socket.on('rename_room', ({ oldName, newName }) => { | ||
| chatData.rooms = chatData.rooms.map(room => room === oldName ? newName : room); | ||
| chatData.messages = chatData.messages.map(msg => msg.room === oldName ? { ...msg, room: newName } : msg); |
There was a problem hiding this comment.
When renaming a room, the server should enforce uniqueness of newName (if not already) and consider notifying sockets in the renamed room — currently you update stored messages/rooms which is good, but server-side validation of newName and branching on conflicts makes the API more robust.
| io.on('connection', (socket) => { | ||
| console.log('A user connected:', socket.id); | ||
|
|
||
| socket.emit('init_data', chatData); |
There was a problem hiding this comment.
Optionally: implement a set_username (or similar) event handler on the server to associate a username with socket (e.g. socket.username = name) when the client sends it. This helps if you want to track connected users or prevent anonymous actions; currently the server never receives or records the username (CHECKLIST #2–#3 should be enforced/handled on server too).
mateacademy-ai-mentor
left a comment
There was a problem hiding this comment.
Review Summary
I am approving your solution as an educational override so you can keep progressing while you refine it further. Your client and server successfully implement the core chat features: username handling, room management (create/rename/join/delete), and loading existing messages for new users, which shows solid understanding of React state management and basic Socket.IO patterns. There are a few robustness issues to improve: in client/src/components/ChatWindow.jsx, avoid using the array index as the key for messages (React can mis-update the DOM when items are added/removed) and consider giving each message a stable id, and also double-check that showing all author === 'System' messages in every room matches your UX intent. In client/src/App.jsx, you currently emit set_username twice (login handler and useEffect), you optimistically switch to a new room before the server confirms creation, and you don’t update currentRoom when a room you’re in is renamed; try to rely on one set_username emit and update room state only after server acknowledgement. On the server (server/index.js), add simple validation and guards: trim and validate usernames, prevent duplicate or empty room names on rename, and protect reserved rooms like General from deletion so a malicious client can’t corrupt shared state. Overall, this is a strong implementation that meets the main requirements; tightening these edge cases will make your app much more resilient and is a great next learning step.
✨ Please rate this review in the Platform's notification center1. I will be grateful for your feedback! ✨
Footnotes
| {messages | ||
| .filter(msg => msg.room === currentRoom || msg.author === 'System') | ||
| .map((msg, i) => ( | ||
| <div key={i} style={{ marginBottom: '15px' }}> |
There was a problem hiding this comment.
Using the array index as the list key can cause incorrect DOM updates when messages are added/removed. Prefer a stable unique key per message (for example a server-assigned id, or a deterministic key like ${msg.time}-${msg.author} if ids are not available). This will avoid React reconciliation issues.
|
|
||
| <div style={{ flex: 1, padding: '20px', overflowY: 'auto', backgroundColor: '#fafafa' }}> | ||
| {messages | ||
| .filter(msg => msg.room === currentRoom || msg.author === 'System') |
There was a problem hiding this comment.
This filter shows any message with author === 'System' in every room. If the intention is that system messages are global, that's fine; if you want system messages tied to a specific room, ensure system messages include room and remove the global-author clause. Confirm this behavior matches your UX expectation.
|
|
||
| useEffect(() => { | ||
| if (username) { | ||
| socket.emit('set_username', username); |
There was a problem hiding this comment.
You emit set_username inside the login handler and again in the username/useEffect; emitting twice is redundant. Consider keeping the emit in one place (for example, the login handler) and having the useEffect only handle join_room when username is set to avoid duplicate events.
| const roomName = prompt('Enter new room name:'); | ||
| if (roomName && !rooms.includes(roomName)) { | ||
| socket.emit('create_room', roomName); | ||
| setCurrentRoom(roomName); |
There was a problem hiding this comment.
When creating a room you call setCurrentRoom(roomName) immediately after emitting create_room. If the server refuses or doesn't add the room (race or validation), the client will switch to a non-existent room. Consider waiting for the server confirmation (e.g., handle update_rooms or init_data) before changing currentRoom, or only change currentRoom after receiving server acknowledgement.
| const handleRenameRoom = (oldName) => { | ||
| const newName = prompt(`Rename "${oldName}" to:`, oldName); | ||
|
|
||
| if (newName && newName !== oldName && !rooms.includes(newName)) { | ||
| socket.emit('rename_room', { oldName, newName }); | ||
| } |
There was a problem hiding this comment.
Renaming a room emits rename_room, but if the user renames the room they are currently in you should update currentRoom to the newName as well. Right now currentRoom may still point to the old name after server-side rename, causing the UI to reference a removed room. Update currentRoom when oldName === currentRoom (or handle it after server init_data arrives).
| socket.on('set_username', (name) => { | ||
| socket.username = name; | ||
| console.log(`Socket ${socket.id} is now known as ${name}.`) |
There was a problem hiding this comment.
You set socket.username here, which satisfies the requirement that the server knows the username. Consider trimming/validating name (reject empty/whitespace) and optionally emitting an acknowledgement back to the client so the client knows the server accepted the name.
| socket.on('rename_room', ({ oldName, newName }) => { | ||
| chatData.rooms = chatData.rooms.map(room => room === oldName ? newName : room); | ||
| chatData.messages = chatData.messages.map(msg => msg.room === oldName ? { ...msg, room: newName } : msg); | ||
|
|
||
| io.emit('init_data', chatData); |
There was a problem hiding this comment.
rename_room updates rooms/messages blindly. The client prevents duplicate names, but the server should also validate newName (e.g., non-empty, not already taken) and return an error or skip the change if invalid to avoid inconsistent state.
| socket.on('delete_room', (roomName) => { | ||
| chatData.rooms = chatData.rooms.filter(room => room !== roomName); | ||
| chatData.messages = chatData.messages.filter(msg => msg.room !== roomName); | ||
|
|
||
| io.emit('init_data', chatData); |
There was a problem hiding this comment.
delete_room removes any room the client requests. The client prevents deletion of General, but the server should also protect reserved rooms (like 'General') server-side to prevent malicious or buggy clients from deleting them.
| socket.join(roomName); | ||
| console.log(`User ${socket.id} joined room: ${roomName}.`); |
There was a problem hiding this comment.
When users join or leave you log the socket id. Consider including socket.username in these logs and/or emitting a small system message to the room so other users see who joined/left; this improves traceability and UX.
No description provided.