diff --git a/client/src/components/ChatMessage.tsx b/client/src/components/ChatMessage.tsx
new file mode 100644
index 0000000..684308e
--- /dev/null
+++ b/client/src/components/ChatMessage.tsx
@@ -0,0 +1,33 @@
+import React from "react";
+import { ChatMessage } from "../../../shared";
+
+function ChatMessageDisplay({
+ message,
+ userId,
+}: {
+ message: ChatMessage;
+ userId: string | undefined; // pass userId as string to avoid rerendering. if we useChatState here, it would rerender every single message every time a new chat message is added.
+}) {
+ const isOwn = userId === message.author.userId;
+
+ return (
+
+
+ {!isOwn && {message.author.name}}
+ {new Date(message.timestamp).toLocaleTimeString()}
+ {isOwn && {message.author.name}}
+
+
+ {message.content}
+
+
+ );
+}
+
+export default React.memo(ChatMessageDisplay);
diff --git a/client/src/components/ChatMessages.tsx b/client/src/components/MessageDisplay.tsx
similarity index 53%
rename from client/src/components/ChatMessages.tsx
rename to client/src/components/MessageDisplay.tsx
index e10ff24..177f0ed 100644
--- a/client/src/components/ChatMessages.tsx
+++ b/client/src/components/MessageDisplay.tsx
@@ -1,19 +1,15 @@
import React from "react";
import { useChatState } from "../contexts/ChatContext";
+import ChatMessage from "./ChatMessage";
function ChatMessages() {
const { state } = useChatState();
+ const userId = state.currentUser?.userId;
return (
<>
{state.messages.map((message) => (
-
-
- {message.author.name}
- {new Date(message.timestamp).toLocaleTimeString()}
-
-
{message.content}
-
+
))}
>
);
diff --git a/client/src/contexts/ChatContext.tsx b/client/src/contexts/ChatContext.tsx
index 6049b29..771f464 100644
--- a/client/src/contexts/ChatContext.tsx
+++ b/client/src/contexts/ChatContext.tsx
@@ -34,3 +34,8 @@ export const useChatState = () => {
}
return context;
};
+
+export const useCurrentUser = () => {
+ const { state } = useChatState();
+ return state.currentUser;
+};
diff --git a/client/src/hooks/useWebSocket.ts b/client/src/hooks/useWebSocket.ts
index 3a45d2a..c805b3b 100644
--- a/client/src/hooks/useWebSocket.ts
+++ b/client/src/hooks/useWebSocket.ts
@@ -9,7 +9,7 @@ import { useChatState } from "../contexts/ChatContext";
export function useWebSocket(roomId: string) {
const [ws, setWs] = useState(null);
const [isConnected, setIsConnected] = useState(false);
- const { state, actions } = useChatState();
+ const { actions } = useChatState();
// Handle incoming messages
const handleMessage = useCallback(
@@ -17,20 +17,22 @@ export function useWebSocket(roomId: string) {
const message = JSON.parse(event.data) as ServerMessage;
switch (message.type) {
- case "USER_JOINED":
- console.log("User joined", message.payload);
- if (message.payload.user.name === state.currentUser?.name) {
- actions.setUser(message.payload.user);
- }
+ case "REGISTRATION_CONFIRMED":
+ actions.setUser(message.payload.user);
break;
+
+ case "USER_JOINED":
+ break;
+
case "CHAT_MESSAGE":
actions.addMessage(message.payload);
break;
+
default:
console.error("Unknown message type", message);
}
},
- [state.currentUser, actions],
+ [actions],
);
// Send a message to the server
diff --git a/client/src/pages/Room.tsx b/client/src/pages/Room.tsx
index 03b2f49..99dbb07 100644
--- a/client/src/pages/Room.tsx
+++ b/client/src/pages/Room.tsx
@@ -2,7 +2,7 @@ import { useParams } from "react-router-dom";
import Layout from "../components/Layout";
import Connect from "../components/ConnectWithName";
import { useWebSocket } from "../hooks/useWebSocket";
-import ChatMessages from "../components/ChatMessages";
+import ChatMessages from "../components/MessageDisplay";
import ChatInput from "../components/ChatInput";
import { useChatState } from "../contexts/ChatContext";
diff --git a/server/src/handleClientMessage.ts b/server/src/handleClientMessage.ts
index b29395d..eff88e8 100644
--- a/server/src/handleClientMessage.ts
+++ b/server/src/handleClientMessage.ts
@@ -2,6 +2,7 @@ import { randomUUIDv7, type ServerWebSocket } from "bun";
import type {
ClientMessage,
ServerChatMessage,
+ ServerRegistrationConfirmed,
ServerUserJoinedMessage,
User,
} from "../../shared";
@@ -18,21 +19,23 @@ export default function handleClientMessage(
switch (data.type) {
case "JOIN":
- const userId = randomUUIDv7();
- const name = data.payload.username;
+ const user = {
+ name: data.payload.username,
+ userId: randomUUIDv7(),
+ };
+ room.userConnections.set(client, user);
- room.userConnections.set(client, {
- name,
- userId,
- });
+ const registrationConfirm: ServerRegistrationConfirmed = {
+ type: "REGISTRATION_CONFIRMED",
+ payload: { user },
+ };
+ client.send(JSON.stringify(registrationConfirm));
+ // notify everyone else that a new user has joined
const joinMessage: ServerUserJoinedMessage = {
type: "USER_JOINED",
payload: {
- user: {
- name,
- userId,
- },
+ user,
},
};
diff --git a/shared.ts b/shared.ts
index d5547af..9792666 100644
--- a/shared.ts
+++ b/shared.ts
@@ -58,6 +58,15 @@ export interface ChatMessage {
author: User;
}
+// sent to the newly registered client to confirm that the username was not taken
+export interface ServerRegistrationConfirmed {
+ type: "REGISTRATION_CONFIRMED";
+ payload: {
+ user: User;
+ };
+}
+
+// sent to all clients when a new client has joined the chat
export interface ServerUserJoinedMessage {
type: "USER_JOINED";
payload: {
@@ -100,6 +109,7 @@ export interface ServerUserListMessage {
}
export type ServerMessage =
+ | ServerRegistrationConfirmed
| ServerUserJoinedMessage
| ServerUserLeftMessage
| ServerChatMessage