Server Side Events (SSE) for Remix, made easy.
- ✅ Zero-dependencies
- ✅ Small bundle
- ✅ Context for re-using event source connections across remix application
- ✅ supports multiple event types from a single emitter
- ✅ 100% typescript
- ✅ (experimental) Typed deserialization
npm i @remix-sse/client @remix-sse/server
See examples directory.
See basic example for more detail.
- Setup your event source route, here it is called
/routes/emitter.tsx
for simplicity
Note: This MUST be a resource route, you cannot return a component from this route.
import { EventStream } from '@remix-sse/server'
export const loader: LoaderFunction = ({ request }) => {
// Return the EventStream from your route loader
return new EventStream(request, (send) => {
// In the init function, setup your SSE Event source
// This can be any asynchronous data source, that will send
// events to the client periodically
// Here we will just use a `setInterval`
const interval = setInterval(() => {
// You can send events to the client via the `send` function
send(JSON.stringify({ hello: 'world'}))
// if you to send different events on multiple channels you can
// specify an eventKey in the options
send(JSON.stringify({ hello: 'world'}), { eventKey: 'channelExample'})
}, 1000)
return () => {
// Return a cleanup function
clearInterval(interval)
};
});
};
Call useEventStream
from anywhere in your tree to begin listening to events emitted from the event source
// This value is a react state object, and will change everytime
// an event is emitted
// By default this is a string[]
const greeting = useEventStream('/emitter')
// But you can return only the latest event as follows
const latestGreeting = useEventStream('/emitter', {
returnLatestOnly: true
})
// Or you can type the return by deserializing the event data
const typedGreeting = useEventStream('/emitter', {
returnLatestOnly: true,
deserialize: (raw) => JSON.parse(raw) as Greeting
})
useEventStream
will create an EventSource for you. Its worth noting that it will also ensure only one EventSource is created for each URL.
If you want to manage the EventSources yourself you can use the lower-level useSubscribe
hook which takes an EventSource
and behaves the same as useEventStream
const source = new EventSource('/my-url')
const data = useSubscribe(source)
This is largely uneccessary
By default all messages are sent on the message
channel. You can send events on different channels by specifying the channel
when using the send
function and in useEventStream
/ useSubscribe
.
export const loader: LoaderFunction = ({ request }) => {
return new EventStream(request, (send) => {
setInterval(() => {
send((Math.random() * gIndex * 1000).toString(), { channel: 'assetValue' });
}, 1000);
setInterval(() => {
send(
JSON.stringify([
{ stock: 'AAPL', latestPrice: Math.random() * 100 },
{
stock: 'TSLA',
latestPrice: Math.random() * 100,
},
]), {
channel: 'holdings'
}
);
}, 500);
});
};
const assetValue = useEventStream('/emitter', {
channel: 'assetValue',
returnLatestOnly: true,
});
const holdings = useEventStream('/emitter', {
channel: 'holdings',
});
See deserialize example for more details
By default the data
returned from useSubscribe
is a string[]
You can pass a deserialize
function to de-deserialize each event as it comes in.
Note: this feature is experimental and is subject to change.
See deserialize for more details.
Option | Description | Default |
---|---|---|
eventKey |
The name of the event, use this if you wish to send multiple types of event from the same emitter | 'message' |
maxEventRetention |
The maximum number of events that will be kept. | 50 |
returnLatestOnly |
Returns only the most recently emitted event - ie. returns TEvent instead of TEvent[] |
false |
These are currently being tested, and are subject to change at any point.
Option | Description | Default |
---|---|---|
deserialize |
A function that will receive the raw event data and returns a deserialized value. See deserialize example | undefined |
.