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";
|
||||
|
||||
function ChatMessages() {
|
||||
const { messages } = useChatState();
|
||||
const { state } = useChatState();
|
||||
|
||||
return (
|
||||
<>
|
||||
{messages.map((message) => (
|
||||
{state.messages.map((message) => (
|
||||
<div key={message.id}>
|
||||
<div>
|
||||
<span>{message.author.name}</span>
|
||||
|
|
|
|||
|
|
@ -1,20 +1,26 @@
|
|||
import React from "react";
|
||||
import React, { useReducer } from "react";
|
||||
import { ChatMessage, User } from "../../../shared";
|
||||
import { ChatContext } from "../contexts/ChatContext";
|
||||
import { chatReducer } from "../reducers/chatReducers";
|
||||
|
||||
export function ChatProvider({ children }: { children: React.ReactNode }) {
|
||||
const [messages, setMessages] = React.useState<ChatMessage[]>([]);
|
||||
const [currentUser, setCurrentUser] = React.useState<User | null>(null);
|
||||
const [state, dispatch] = useReducer(chatReducer, {
|
||||
messages: [],
|
||||
currentUser: null,
|
||||
isConnected: false,
|
||||
});
|
||||
|
||||
const addMessage = (message: ChatMessage) => {
|
||||
setMessages((prev) => [...prev, message]);
|
||||
const value = {
|
||||
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 (
|
||||
<ChatContext.Provider
|
||||
value={{ messages, currentUser, addMessage, setCurrentUser }}
|
||||
>
|
||||
{children}
|
||||
</ChatContext.Provider>
|
||||
);
|
||||
return <ChatContext.Provider value={value}>{children}</ChatContext.Provider>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ interface ConnectProps {
|
|||
}
|
||||
|
||||
function Connect({ onConnect }: ConnectProps) {
|
||||
const { setCurrentUser } = useChatState();
|
||||
const { actions } = useChatState();
|
||||
const [name, setName] = useState("");
|
||||
|
||||
function handleConnect() {
|
||||
|
|
@ -14,8 +14,11 @@ function Connect({ onConnect }: ConnectProps) {
|
|||
return;
|
||||
}
|
||||
|
||||
// leave userId empty for now, it is server generated
|
||||
setCurrentUser({ name, userId: "" });
|
||||
actions.setUser({
|
||||
name: name,
|
||||
userId: "", // leave userId empty for now, it is server generated
|
||||
});
|
||||
|
||||
onConnect(name);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,17 +1,36 @@
|
|||
import { createContext, useContext } from "react";
|
||||
import { createContext, useContext, Dispatch } from "react";
|
||||
import { ChatMessage, User } from "../../../shared";
|
||||
|
||||
interface ChatContextType {
|
||||
export type ChatState = {
|
||||
messages: ChatMessage[];
|
||||
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;
|
||||
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 useChatState = () => {
|
||||
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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import { useChatState } from "../contexts/ChatContext";
|
|||
export function useWebSocket(roomId: string) {
|
||||
const [ws, setWs] = useState<WebSocket | null>(null);
|
||||
const [isConnected, setIsConnected] = useState(false);
|
||||
const { addMessage, currentUser, setCurrentUser } = useChatState();
|
||||
const { state, actions } = useChatState();
|
||||
|
||||
// Handle incoming messages
|
||||
const handleMessage = useCallback(
|
||||
|
|
@ -19,18 +19,18 @@ export function useWebSocket(roomId: string) {
|
|||
switch (message.type) {
|
||||
case "USER_JOINED":
|
||||
console.log("User joined", message.payload);
|
||||
if (message.payload.user.name === currentUser?.name) {
|
||||
setCurrentUser(message.payload.user);
|
||||
if (message.payload.user.name === state.currentUser?.name) {
|
||||
actions.setUser(message.payload.user);
|
||||
}
|
||||
break;
|
||||
case "CHAT_MESSAGE":
|
||||
addMessage(message.payload);
|
||||
actions.addMessage(message.payload);
|
||||
break;
|
||||
default:
|
||||
console.error("Unknown message type", message);
|
||||
}
|
||||
},
|
||||
[currentUser, setCurrentUser, addMessage],
|
||||
[state.currentUser, actions],
|
||||
);
|
||||
|
||||
// Send a message to the server
|
||||
|
|
|
|||
|
|
@ -9,15 +9,15 @@ import { useChatState } from "../contexts/ChatContext";
|
|||
function Room() {
|
||||
const { roomId } = useParams();
|
||||
const { isConnected, connect, sendMessage } = useWebSocket(roomId ?? "");
|
||||
const { currentUser } = useChatState();
|
||||
const { state } = useChatState();
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<section className="w-full">
|
||||
<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>
|
||||
{currentUser && (
|
||||
<span className="font-medium">User: {currentUser?.name}</span>
|
||||
{state.currentUser && (
|
||||
<span className="font-medium">User: {state.currentUser?.name}</span>
|
||||
)}
|
||||
<span
|
||||
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