chore: migrate to reducer pattern instead of useState
This commit is contained in:
parent
18dd8b83f0
commit
7150ca87c3
7 changed files with 85 additions and 29 deletions
|
|
@ -2,11 +2,11 @@ import React from "react";
|
||||||
import { useChatState } from "../contexts/ChatContext";
|
import { useChatState } from "../contexts/ChatContext";
|
||||||
|
|
||||||
function ChatMessages() {
|
function ChatMessages() {
|
||||||
const { messages } = useChatState();
|
const { state } = useChatState();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{messages.map((message) => (
|
{state.messages.map((message) => (
|
||||||
<div key={message.id}>
|
<div key={message.id}>
|
||||||
<div>
|
<div>
|
||||||
<span>{message.author.name}</span>
|
<span>{message.author.name}</span>
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,26 @@
|
||||||
import React from "react";
|
import React, { useReducer } from "react";
|
||||||
import { ChatMessage, User } from "../../../shared";
|
import { ChatMessage, User } from "../../../shared";
|
||||||
import { ChatContext } from "../contexts/ChatContext";
|
import { ChatContext } from "../contexts/ChatContext";
|
||||||
|
import { chatReducer } from "../reducers/chatReducers";
|
||||||
|
|
||||||
export function ChatProvider({ children }: { children: React.ReactNode }) {
|
export function ChatProvider({ children }: { children: React.ReactNode }) {
|
||||||
const [messages, setMessages] = React.useState<ChatMessage[]>([]);
|
const [state, dispatch] = useReducer(chatReducer, {
|
||||||
const [currentUser, setCurrentUser] = React.useState<User | null>(null);
|
messages: [],
|
||||||
|
currentUser: null,
|
||||||
|
isConnected: false,
|
||||||
|
});
|
||||||
|
|
||||||
const addMessage = (message: ChatMessage) => {
|
const value = {
|
||||||
setMessages((prev) => [...prev, message]);
|
state,
|
||||||
|
dispatch,
|
||||||
|
actions: {
|
||||||
|
addMessage: (message: ChatMessage) =>
|
||||||
|
dispatch({ type: "ADD_MESSAGE", payload: message }),
|
||||||
|
setUser: (user: User) => dispatch({ type: "SET_USER", payload: user }),
|
||||||
|
setConnected: (isConnected: boolean) =>
|
||||||
|
dispatch({ type: "SET_CONNECTED", payload: isConnected }),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return <ChatContext.Provider value={value}>{children}</ChatContext.Provider>;
|
||||||
<ChatContext.Provider
|
|
||||||
value={{ messages, currentUser, addMessage, setCurrentUser }}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</ChatContext.Provider>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ interface ConnectProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
function Connect({ onConnect }: ConnectProps) {
|
function Connect({ onConnect }: ConnectProps) {
|
||||||
const { setCurrentUser } = useChatState();
|
const { actions } = useChatState();
|
||||||
const [name, setName] = useState("");
|
const [name, setName] = useState("");
|
||||||
|
|
||||||
function handleConnect() {
|
function handleConnect() {
|
||||||
|
|
@ -14,8 +14,11 @@ function Connect({ onConnect }: ConnectProps) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// leave userId empty for now, it is server generated
|
actions.setUser({
|
||||||
setCurrentUser({ name, userId: "" });
|
name: name,
|
||||||
|
userId: "", // leave userId empty for now, it is server generated
|
||||||
|
});
|
||||||
|
|
||||||
onConnect(name);
|
onConnect(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,36 @@
|
||||||
import { createContext, useContext } from "react";
|
import { createContext, useContext, Dispatch } from "react";
|
||||||
import { ChatMessage, User } from "../../../shared";
|
import { ChatMessage, User } from "../../../shared";
|
||||||
|
|
||||||
interface ChatContextType {
|
export type ChatState = {
|
||||||
messages: ChatMessage[];
|
messages: ChatMessage[];
|
||||||
currentUser: User | null;
|
currentUser: User | null;
|
||||||
|
isConnected: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ChatAction =
|
||||||
|
| { type: "ADD_MESSAGE"; payload: ChatMessage }
|
||||||
|
| { type: "SET_USER"; payload: User }
|
||||||
|
| { type: "SET_CONNECTED"; payload: boolean }
|
||||||
|
| { type: "CLEAR_MESSAGES" };
|
||||||
|
|
||||||
|
export type ChatActions = {
|
||||||
addMessage: (message: ChatMessage) => void;
|
addMessage: (message: ChatMessage) => void;
|
||||||
setCurrentUser: (user: User) => void;
|
setUser: (user: User) => void;
|
||||||
|
setConnected: (isConnected: boolean) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface ChatContextType {
|
||||||
|
state: ChatState;
|
||||||
|
dispatch: Dispatch<ChatAction>;
|
||||||
|
actions: ChatActions;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ChatContext = createContext<ChatContextType | null>(null);
|
export const ChatContext = createContext<ChatContextType | null>(null);
|
||||||
|
|
||||||
export const useChatState = () => {
|
export const useChatState = () => {
|
||||||
const context = useContext(ChatContext);
|
const context = useContext(ChatContext);
|
||||||
if (!context) throw new Error("useChat must be used within ChatProvider");
|
if (!context) {
|
||||||
|
throw new Error("useChatState must be used within ChatProvider");
|
||||||
|
}
|
||||||
return context;
|
return context;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import { useChatState } from "../contexts/ChatContext";
|
||||||
export function useWebSocket(roomId: string) {
|
export function useWebSocket(roomId: string) {
|
||||||
const [ws, setWs] = useState<WebSocket | null>(null);
|
const [ws, setWs] = useState<WebSocket | null>(null);
|
||||||
const [isConnected, setIsConnected] = useState(false);
|
const [isConnected, setIsConnected] = useState(false);
|
||||||
const { addMessage, currentUser, setCurrentUser } = useChatState();
|
const { state, actions } = useChatState();
|
||||||
|
|
||||||
// Handle incoming messages
|
// Handle incoming messages
|
||||||
const handleMessage = useCallback(
|
const handleMessage = useCallback(
|
||||||
|
|
@ -19,18 +19,18 @@ export function useWebSocket(roomId: string) {
|
||||||
switch (message.type) {
|
switch (message.type) {
|
||||||
case "USER_JOINED":
|
case "USER_JOINED":
|
||||||
console.log("User joined", message.payload);
|
console.log("User joined", message.payload);
|
||||||
if (message.payload.user.name === currentUser?.name) {
|
if (message.payload.user.name === state.currentUser?.name) {
|
||||||
setCurrentUser(message.payload.user);
|
actions.setUser(message.payload.user);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "CHAT_MESSAGE":
|
case "CHAT_MESSAGE":
|
||||||
addMessage(message.payload);
|
actions.addMessage(message.payload);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
console.error("Unknown message type", message);
|
console.error("Unknown message type", message);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[currentUser, setCurrentUser, addMessage],
|
[state.currentUser, actions],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Send a message to the server
|
// Send a message to the server
|
||||||
|
|
|
||||||
|
|
@ -9,15 +9,15 @@ import { useChatState } from "../contexts/ChatContext";
|
||||||
function Room() {
|
function Room() {
|
||||||
const { roomId } = useParams();
|
const { roomId } = useParams();
|
||||||
const { isConnected, connect, sendMessage } = useWebSocket(roomId ?? "");
|
const { isConnected, connect, sendMessage } = useWebSocket(roomId ?? "");
|
||||||
const { currentUser } = useChatState();
|
const { state } = useChatState();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<Layout>
|
||||||
<section className="w-full">
|
<section className="w-full">
|
||||||
<h2 className="text-xl bg-gray-100 p-4 flex justify-between items-center rounded-md shadow-sm">
|
<h2 className="text-xl bg-gray-100 p-4 flex justify-between items-center rounded-md shadow-sm">
|
||||||
<span className="font-medium">Room ID: {roomId}</span>
|
<span className="font-medium">Room ID: {roomId}</span>
|
||||||
{currentUser && (
|
{state.currentUser && (
|
||||||
<span className="font-medium">User: {currentUser?.name}</span>
|
<span className="font-medium">User: {state.currentUser?.name}</span>
|
||||||
)}
|
)}
|
||||||
<span
|
<span
|
||||||
className={`px-3 py-1 rounded-full text-sm font-medium ${
|
className={`px-3 py-1 rounded-full text-sm font-medium ${
|
||||||
|
|
|
||||||
28
client/src/reducers/chatReducers.ts
Normal file
28
client/src/reducers/chatReducers.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { ChatAction, ChatState } from "../contexts/ChatContext";
|
||||||
|
|
||||||
|
export function chatReducer(state: ChatState, action: ChatAction): ChatState {
|
||||||
|
switch (action.type) {
|
||||||
|
case "ADD_MESSAGE":
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
messages: [...state.messages, action.payload],
|
||||||
|
};
|
||||||
|
case "SET_USER":
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
currentUser: action.payload,
|
||||||
|
};
|
||||||
|
case "SET_CONNECTED":
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
isConnected: action.payload,
|
||||||
|
};
|
||||||
|
case "CLEAR_MESSAGES":
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
messages: [],
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue