Created
May 8, 2022 22:20
-
-
Save jacob-ebey/02142c80adba98967a61987be51ade26 to your computer and use it in GitHub Desktop.
Simple Remix SSE Chat Application on new fetch polyfill
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import type { LoaderFunction } from "@remix-run/node"; | |
import type { ChatMessageEvent } from "~/events.server"; | |
import { chatEvents } from "~/events.server"; | |
export let loader: LoaderFunction = ({ request }) => { | |
if (!request.signal) { | |
throw new Error("No request signal provided by the platform"); | |
} | |
let encoder = new TextEncoder(); | |
let body = new ReadableStream<Uint8Array>({ | |
start(controller) { | |
const handleMessage = (event: ChatMessageEvent) => { | |
const message = event.message; | |
controller.enqueue(encoder.encode(`id: ${message.id}\n`)); | |
controller.enqueue(encoder.encode("event: message\n")); | |
controller.enqueue( | |
encoder.encode(`data: ${JSON.stringify(message)}\n\n`) | |
); | |
}; | |
chatEvents.addEventListener("message", handleMessage as any); | |
request.signal.addEventListener("abort", () => { | |
chatEvents.removeEventListener("message", handleMessage as any); | |
controller.close(); | |
}); | |
}, | |
}); | |
let response = new Response(body, { | |
headers: { | |
"Content-Type": "text/event-stream", | |
}, | |
}); | |
return response; | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useEffect, useState, useRef } from "react"; | |
import type { ActionFunction, LoaderFunction } from "@remix-run/node"; | |
import { json } from "@remix-run/node"; | |
import { Form, useLoaderData } from "@remix-run/react"; | |
import cuid from "cuid"; | |
import type { ChatMessage } from "~/events.server"; | |
import { chatEvents, ChatMessageEvent } from "~/events.server"; | |
import { requireUsername } from "~/session.server"; | |
export let action: ActionFunction = async ({ request }) => { | |
let formData = await request.formData(); | |
let username = formData.get("username"); | |
let message = formData.get("message"); | |
if (typeof username !== "string" || typeof message !== "string") { | |
return json({ error: "username and message are required" }); | |
} | |
let chatMessage: ChatMessage = { | |
id: cuid(), | |
username, | |
message, | |
timestamp: Date.now(), | |
}; | |
chatEvents.dispatchEvent(new ChatMessageEvent(chatMessage)); | |
return json({}); | |
}; | |
type LoaderData = { | |
username: string; | |
}; | |
export let loader: LoaderFunction = async ({ request }) => { | |
let username = await requireUsername(request); | |
return json<LoaderData>({ username }); | |
}; | |
export default function Chat() { | |
let { username } = useLoaderData() as LoaderData; | |
return ( | |
<main> | |
<h1>Welcome {username}</h1> | |
<section> | |
<Form method="post"> | |
<input type="hidden" name="username" value={username} /> | |
<label> | |
Message | |
<br /> | |
<textarea name="message" /> | |
</label> | |
<br /> | |
<button type="submit">Send</button> | |
</Form> | |
</section> | |
<section> | |
<ChatMessages /> | |
</section> | |
</main> | |
); | |
} | |
function ChatMessages() { | |
let messagesRef = useRef<ChatMessage[]>([]); | |
let [, rerender] = useState({}); | |
useEffect(() => { | |
const eventSource = new EventSource("/api/chat"); | |
eventSource.addEventListener("message", (event) => { | |
if (event.type === "message") { | |
messagesRef.current.push(JSON.parse(event.data)); | |
rerender({}); | |
} | |
}); | |
return () => { | |
eventSource.close(); | |
}; | |
}, []); | |
return ( | |
<ul> | |
{messagesRef.current.map((message) => ( | |
<li key={message.id}> | |
{message.username}: {message.message} | |
</li> | |
))} | |
</ul> | |
); | |
} |
What's the reasoning behind using a ref but forcing a rerender whenever the ref is updated?
The only thing I can think of is performance.
Creating an empty object will be much faster than copying over an entire array with 500 items and pushing a new message at the end.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
What's the reasoning behind using a ref but forcing a rerender whenever the ref is updated?