Skip to content

Commit 36fe43d

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

File tree

9 files changed

+15629
-4689
lines changed

9 files changed

+15629
-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+
}
+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
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+
</body>
15+
</html>

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)