Skip to content

Instantly share code, notes, and snippets.

@Mosharush
Last active August 13, 2025 00:14
Show Gist options
  • Save Mosharush/8bbc178bbc7e47c7c7c554dd7b5c5528 to your computer and use it in GitHub Desktop.
Save Mosharush/8bbc178bbc7e47c7c7c554dd7b5c5528 to your computer and use it in GitHub Desktop.
React Hook - Server-Sent Events (SSE)

Server-Sent Events (SSE) are commonly used in real-time applications where the server needs to push updates to the client.

Here are a few use cases:

  1. Real-time notifications: SSE can be used to push notifications to the client in real-time. For example, in a social media application, the server can push notifications about new posts, likes, comments, etc.
  2. Live news updates: In a news application, the server can push live news updates to the client.
  3. Real-time analytics: In an analytics dashboard, the server can push real-time data updates to the client.
  4. Chat applications: In a chat application, the server can push new messages to the client in real-time.
  5. Online multiplayer games: In an online multiplayer game, the server can push game state updates to the client in real-time.
  6. Stock price updates: In a stock trading application, the server can push real-time stock price updates to the client.

useSse - Server-Sent Events Hook

useSse is a custom React hook for handling Server-Sent Events (SSE) in your application.

Usage

Here's a basic example of how to use the useSse hook:

import { useEffect } from 'react';
import useSse from './useSse';

const MyComponent = () => {
  const {
    connectionState,
    connectionError,
    addListener,
    getEventData,
    closeConnection,
  } = useSse('https://my-api.com/events', { withCredentials: true });

  // register the listener once, clean it up on unmount
  useEffect(() => {
    const remove = addListener('myEvent', data => {
      console.log('Received data:', data);
    });

    return () => {
      remove?.();          // detach the listener
      closeConnection();   // close the SSE connection
    };
  }, [addListener, closeConnection]);

  // grab the latest payload for this event on every render
  const data = getEventData('myEvent');

  return (
    <div>
      <p>Connection state: {connectionState}</p>
      {connectionError && <p>Error: {connectionError.message}</p>}
      {data && <p>Last payload: {data}</p>}
    </div>
  );
};

export default MyComponent;

API

The useSse hook returns an object with these properties:

  • connectionState: current state of the SSE connection (CONNECTING, OPEN, CLOSED)
  • connectionError: an Event object describing the most recent connection error, or null if none occurred
  • addListener(eventName, handler): registers a listener for the specified event; the handler receives the event data string. The function returns a cleanup callback you can call (or return from a useEffect) to remove the listener when needed
  • getEventData(eventName): returns the latest payload string for the given event name. Because it comes from React state, a component that reads this value will automatically re-render whenever the payload changes
  • closeConnection(): closes the underlying EventSource and updates connectionState to CLOSED
const express = require('express');
const SSE = require('sse-stream');
const app = express();
const sse = SSE();
app.get('/events', sse, (req, res) => {
setInterval(() => {
const data = Math.random().toString(); // Generate random data
res.sse.event('MyEvent', data);
}, 1000);
});
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
import { useEffect, useState, useCallback } from 'react';
interface EventData {
[key: string]: string;
}
const useSse = (url: string, options?: EventSourceInit) => {
const [connectionState, setConnectionState] = useState('CONNECTING');
const [connectionError, setConnectionError] = useState<Event | null>(null);
const [eventSource, setEventSource] = useState<EventSource | null>(null);
const [eventData, setEventData] = useState<EventData>({});
// open the connection once and tidy up on unmount or when url / options change
useEffect(() => {
const es = new EventSource(url, options);
setEventSource(es);
es.onopen = () => setConnectionState('OPEN');
es.onerror = (err: Event) => {
setConnectionState('CLOSED');
setConnectionError(err);
};
return () => es.close();
}, [url, options]);
// register a listener for a specific event name
const addListener = useCallback(
(eventName: string, handler: (data: string) => void) => {
if (!eventSource) return;
const listener = (evt: MessageEvent) => {
setEventData(prev => ({
...prev,
[eventName]: evt.data,
}));
handler(evt.data);
};
eventSource.addEventListener(eventName, listener);
return () => eventSource.removeEventListener(eventName, listener);
},
[eventSource],
);
// simple getter: no hooks inside so rules of hooks are respected
const getEventData = useCallback(
(eventName: string) => eventData[eventName],
[eventData],
);
const closeConnection = useCallback(() => eventSource?.close(), [eventSource]);
return {
connectionState,
connectionError,
addListener,
getEventData,
closeConnection,
};
};
export default useSse;
@TomRadford
Copy link

TomRadford commented Aug 4, 2025

This is awesome!

Note that

  const getEventData = useCallback((eventName: string) => {
    const [data, setData] = useState<string | undefined>(eventData[eventName]);
    useEffect(() => {
      setData(eventData[eventName]);
    }, [eventData[eventName]]);
    return [data, setData];
  }, [eventData]);

The useState and useEffect hooks are being called inside the getEventData callback function. This violates the Rules of Hooks, which state that hooks must be called at the top level of React functions, not inside loops, conditions, or nested functions.

So I a fix would be to create this util in its own hook with its own lexical scope 🤓

@Mosharush
Copy link
Author

@TomRadford Thanks for pointing that out
I pulled the hooks out of the nested callback, added a cleanup for listeners, and updated the README and example
Let me know if you spot anything else

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment