Skip to content

Commit 1c028f5

Browse files
committed
feat: chat room react example
1 parent a368c8d commit 1c028f5

File tree

10 files changed

+15732
-4689
lines changed

10 files changed

+15732
-4689
lines changed

examples/chat-room-react/.gitignore

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.actorcore
2+
node_modules
3+
# React
4+
build/
+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { actor, setup } from "actor-core";
2+
3+
export type Message = { sender: string; text: string; timestamp: number; }
4+
5+
export const chatRoom = actor({
6+
// State is automatically persisted
7+
state: {
8+
messages: [] as Message[]
9+
},
10+
11+
actions: {
12+
sendMessage: (c, sender: string, text: string) => {
13+
const message = { sender, text, timestamp: Date.now() };
14+
15+
// Any changes to state are automatically saved
16+
c.state.messages.push(message);
17+
18+
// Broadcast events trigger real-time updates in connected clients
19+
c.broadcast("newMessage", message);
20+
},
21+
22+
getHistory: (c) => c.state.messages
23+
}
24+
});
25+
26+
// Create and export the app
27+
export const app = setup({
28+
actors: { chatRoom },
29+
});
30+
31+
// Export type for client type checking
32+
export type App = typeof app;

examples/chat-room-react/package.json

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
{
2+
"name": "chat-room-react",
3+
"version": "0.8.0",
4+
"private": true,
5+
"type": "module",
6+
"main": "src/index.ts",
7+
"scripts": {
8+
"dev:actor": "npx @actor-core/cli@latest dev actors/app.ts",
9+
"dev:frontend": "react-scripts start",
10+
"build": "react-scripts build",
11+
"check-types": "tsc --noEmit",
12+
"test": "vitest run"
13+
},
14+
"dependencies": {
15+
"@actor-core/react": "workspace:*",
16+
"@types/react": "^19",
17+
"@types/react-dom": "^19",
18+
"actor-core": "workspace:*",
19+
"react": "^19",
20+
"react-dom": "^19",
21+
"react-scripts": "^5.0.1"
22+
},
23+
"devDependencies": {
24+
"@actor-core/cli": "workspace:*",
25+
"actor-core": "workspace:*",
26+
"typescript": "^5.5.2"
27+
},
28+
"example": {
29+
"platforms": [
30+
"*"
31+
]
32+
},
33+
"browserslist": {
34+
"production": [
35+
">0.2%",
36+
"not dead",
37+
"not op_mini all"
38+
],
39+
"development": [
40+
"last 1 chrome version",
41+
"last 1 firefox version",
42+
"last 1 safari version"
43+
]
44+
},
45+
"resolutions": {
46+
"react@^19": "^19.0.0",
47+
"react-dom@^19": "^19.0.0",
48+
"react@^18": "^18.3",
49+
"hono": "^4.7.0"
50+
}
51+
}
+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
6+
<meta name="theme-color" content="#000000">
7+
<title>Chat Room</title>
8+
</head>
9+
<body>
10+
<noscript>
11+
You need to enable JavaScript to run this app.
12+
</noscript>
13+
<div id="root"></div>
14+
15+
<link rel="stylesheet" href="./main.css">
16+
</body>
17+
</html>
+101
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
body {
2+
margin: 0;
3+
padding: 20px;
4+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
5+
background-color: #f5f5f5;
6+
}
7+
8+
.chat-container {
9+
max-width: 800px;
10+
margin: 0 auto;
11+
background: white;
12+
border-radius: 12px;
13+
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
14+
display: flex;
15+
flex-direction: column;
16+
height: 80vh;
17+
}
18+
19+
.room-header {
20+
padding: 20px;
21+
border-bottom: 1px solid #eee;
22+
}
23+
24+
.room-header h3 {
25+
margin: 0;
26+
color: #333;
27+
}
28+
29+
.messages {
30+
flex: 1;
31+
overflow-y: auto;
32+
padding: 20px;
33+
}
34+
35+
.message {
36+
margin-bottom: 15px;
37+
padding: 10px;
38+
background: #f8f9fa;
39+
border-radius: 8px;
40+
position: relative;
41+
}
42+
43+
.message b {
44+
color: #2c5282;
45+
margin-right: 8px;
46+
}
47+
48+
.timestamp {
49+
font-size: 0.8em;
50+
color: #666;
51+
position: absolute;
52+
right: 10px;
53+
top: 10px;
54+
}
55+
56+
.empty-message {
57+
text-align: center;
58+
color: #666;
59+
padding: 20px;
60+
font-style: italic;
61+
}
62+
63+
.input-area {
64+
padding: 20px;
65+
border-top: 1px solid #eee;
66+
display: flex;
67+
gap: 10px;
68+
}
69+
70+
.input-area input {
71+
flex: 1;
72+
padding: 12px;
73+
border: 1px solid #ddd;
74+
border-radius: 6px;
75+
font-size: 16px;
76+
outline: none;
77+
transition: border-color 0.2s;
78+
}
79+
80+
.input-area input:focus {
81+
border-color: #4299e1;
82+
}
83+
84+
.input-area button {
85+
padding: 12px 24px;
86+
background: #4299e1;
87+
color: white;
88+
border: none;
89+
border-radius: 6px;
90+
font-size: 16px;
91+
cursor: pointer;
92+
transition: background-color 0.2s;
93+
}
94+
95+
.input-area button:hover {
96+
background: #3182ce;
97+
}
98+
99+
.input-area button:active {
100+
background: #2b6cb0;
101+
}

examples/chat-room-react/src/App.tsx

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { createClient } from "actor-core/client";
2+
import { createReactActorCore } from "@actor-core/react";
3+
import { useState, useEffect } from "react";
4+
import type { App, Message } from "../actors/app";
5+
6+
const client = createClient<App>("http://localhost:6420");
7+
const { useActor, useActorEvent } = createReactActorCore(client);
8+
9+
export default function ChatRoom({ roomId = "general" }) {
10+
// Connect to specific chat room using tags
11+
const [{ actor }] = useActor("chatRoom", {
12+
tags: { roomId }
13+
});
14+
15+
const [messages, setMessages] = useState<Message[]>([]);
16+
const [input, setInput] = useState("");
17+
18+
// Load initial state
19+
useEffect(() => {
20+
if (actor) {
21+
// Load chat history
22+
actor.getHistory().then(setMessages);
23+
}
24+
}, [actor]);
25+
26+
// Listen for real-time updates from the server
27+
useActorEvent({ actor, event: "newMessage" }, (message) => {
28+
setMessages(prev => [...prev, message as Message]);
29+
});
30+
31+
const sendMessage = () => {
32+
if (actor && input.trim()) {
33+
actor.sendMessage("User", input);
34+
setInput("");
35+
}
36+
};
37+
38+
return (
39+
<div className="chat-container">
40+
<div className="room-header">
41+
<h3>Chat Room: {roomId}</h3>
42+
</div>
43+
44+
<div className="messages">
45+
{messages.length === 0 ? (
46+
<div className="empty-message">No messages yet. Start the conversation!</div>
47+
) : (
48+
messages.map((msg, i) => (
49+
<div key={i} className="message">
50+
<b>{msg.sender}:</b> {msg.text}
51+
<span className="timestamp">
52+
{new Date(msg.timestamp).toLocaleTimeString()}
53+
</span>
54+
</div>
55+
))
56+
)}
57+
</div>
58+
59+
<div className="input-area">
60+
<input
61+
value={input}
62+
onChange={e => setInput(e.target.value)}
63+
onKeyDown={e => e.key === "Enter" && sendMessage()}
64+
placeholder="Type a message..."
65+
/>
66+
<button onClick={sendMessage}>Send</button>
67+
</div>
68+
</div>
69+
);
70+
}
+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import ReactDOM from "react-dom/client";
2+
import ChatRoom from "./App";
3+
4+
const container = document.getElementById('root')!;
5+
const root = ReactDOM.createRoot(container);
6+
7+
root.render(<ChatRoom />);
+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
{
2+
"compilerOptions": {
3+
/* Visit https://aka.ms/tsconfig.json to read more about this file */
4+
5+
/* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
6+
"target": "esnext",
7+
/* Specify a set of bundled library declaration files that describe the target runtime environment. */
8+
"lib": ["ESNext", "DOM"],
9+
/* Specify what JSX code is generated. */
10+
"jsx": "react-jsx",
11+
12+
/* Specify what module code is generated. */
13+
"module": "esnext",
14+
/* Specify how TypeScript looks up a file from a given module specifier. */
15+
"moduleResolution": "bundler",
16+
/* Specify type package names to be included without being referenced in a source file. */
17+
"types": ["node"],
18+
/* Enable importing .json files */
19+
"resolveJsonModule": true,
20+
21+
/* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
22+
"allowJs": true,
23+
/* Enable error reporting in type-checked JavaScript files. */
24+
"checkJs": false,
25+
26+
/* Disable emitting files from a compilation. */
27+
"noEmit": true,
28+
29+
/* Ensure that each file can be safely transpiled without relying on other imports. */
30+
"isolatedModules": true,
31+
/* Allow 'import x from y' when a module doesn't have a default export. */
32+
"allowSyntheticDefaultImports": true,
33+
/* Ensure that casing is correct in imports. */
34+
"forceConsistentCasingInFileNames": true,
35+
36+
/* Enable all strict type-checking options. */
37+
"strict": true,
38+
39+
/* Skip type checking all .d.ts files. */
40+
"skipLibCheck": true
41+
},
42+
"include": ["src/**/*", "actors/**/*", "tests/**/*"]
43+
}

package.json

+2-3
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,8 @@
3535
"zx": "^8.3.2"
3636
},
3737
"resolutions": {
38-
"react@^19": "^19.0.0",
39-
"react-dom@^19": "^19.0.0",
40-
"react@^18": "^18.3",
38+
"react": "^19.1.0",
39+
"react-dom": "^19.1.0",
4140
"hono": "^4.7.0"
4241
},
4342
"dependencies": {

0 commit comments

Comments
 (0)