Skip to content

node_chat_TetSh#176

Open
TetiankaSh wants to merge 3 commits intomate-academy:masterfrom
TetiankaSh:develop
Open

node_chat_TetSh#176
TetiankaSh wants to merge 3 commits intomate-academy:masterfrom
TetiankaSh:develop

Conversation

@TetiankaSh
Copy link
Copy Markdown

No description provided.

Copy link
Copy Markdown

@mateacademy-ai-mentor mateacademy-ai-mentor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 calling event.stopPropagation() in the buttons' onClick handlers.

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

  1. Rate AI review example

  2. Re-request AI review example

  3. Add AI review comments example


<div style={{ flex: 1, padding: '20px', overflowY: 'auto', backgroundColor: '#fafafa' }}>
{messages
.filter(msg => msg.room === currentRoom || msg.author === 'System')
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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' }}>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +16 to +18
const [messages, setMessages] = useState([
{ author: 'System', text: 'Welcome to the chat!', time: new Date().toLocaleTimeString() }
]);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +74 to +76
if (currentRoom === roomToDelete) {
setCurrentRoom('General');
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.


{room !== 'General' && (
<div style={{ float: 'right', fontSize: '12px' }}>
<button onClick={() => onRenameRoom(room)}>✏️</button>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

{room !== 'General' && (
<div style={{ float: 'right', fontSize: '12px' }}>
<button onClick={() => onRenameRoom(room)}>✏️</button>
<button onClick={() => onDeleteRoom(room)}>❌</button>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown

@mateacademy-ai-mentor mateacademy-ai-mentor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. 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 the join_room logic so rooms are only joined after the username is set/emitted (or at least gate join_room on username being truthy).
  2. 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 as socket.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

  1. Rate AI review example

  2. Re-request AI review example

  3. Add AI review comments example


<div style={{ flex: 1, padding: '20px', overflowY: 'auto', backgroundColor: '#fafafa' }}>
{messages
.filter(msg => msg.room === currentRoom || msg.author === 'System')
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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' }}>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +15 to +17
<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>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You render msg.author, msg.time, and msg.text directly. If any message is missing these fields it will display undefined. Consider providing default values or conditional rendering to ensure every rendered message has author, time and text (CHECKLIST ITEMS #5#7).

Comment on lines +25 to +26
localStorage.setItem('username', tempName);
setUserName(tempName);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +100 to +101
useEffect(() => {
socket.emit('join_room', currentRoom);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +43 to +46
socket.on('create_room', (roomName) => {
if (!chatData.rooms.includes(roomName)) {
chatData.rooms.push(roomName);
io.emit('update_rooms', chatData.rooms);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +57 to +58
socket.on('delete_room', (roomName) => {
chatData.rooms = chatData.rooms.filter(room => room !== roomName);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +50 to +52
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);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +24 to +27
io.on('connection', (socket) => {
console.log('A user connected:', socket.id);

socket.emit('init_data', chatData);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copy link
Copy Markdown

@mateacademy-ai-mentor mateacademy-ai-mentor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

  1. Rate AI review example

{messages
.filter(msg => msg.room === currentRoom || msg.author === 'System')
.map((msg, i) => (
<div key={i} style={{ marginBottom: '15px' }}>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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')
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +60 to +65
const handleRenameRoom = (oldName) => {
const newName = prompt(`Rename "${oldName}" to:`, oldName);

if (newName && newName !== oldName && !rooms.includes(newName)) {
socket.emit('rename_room', { oldName, newName });
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Comment on lines +29 to +31
socket.on('set_username', (name) => {
socket.username = name;
console.log(`Socket ${socket.id} is now known as ${name}.`)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +57 to +61
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);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +64 to +68
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);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +41 to +42
socket.join(roomName);
console.log(`User ${socket.id} joined room: ${roomName}.`);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants