Mercure in React: an EventSource Hook
Mercure in React: an EventSource Hook
~16 Min. Lesezeit Zuletzt aktualisiert am August 8, 2026
Chapter 69 tested Mercure subscriptions MANUALLY in the browser – this chapter wraps THAT in a reusable React hook that INFORMS TanStack Query SPECIFICALLY about changes.
Writing the useMercure hook
import { useEffect } from 'react';
import { useQueryClient } from '@tanstack/react-query';
const MERCURE_URL = 'https://localhost/.well-known/mercure';
export function useMercure(topic: string, queryKey: unknown[]) {
const queryClient = useQueryClient();
useEffect(() => {
const url = new URL(MERCURE_URL);
url.searchParams.append('topic', topic);
const eventSource = new EventSource(url, { withCredentials: true });
eventSource.onmessage = () => {
queryClient.invalidateQueries({ queryKey });
};
return () => {
eventSource.close();
};
}, [topic, queryClient, queryKey]);
}INSTEAD of parsing the update message itself and WRITING it into the cache, THIS hook uses invalidateQueries() from chapter 78 – SIMPLER and LESS error-prone, since the SUBSEQUENT refetch ALWAYS fetches the GUARANTEED current, authorized state from the server.
Achtung: withCredentials: true is NECESSARY for EventSource to SEND ALONG the mercureAuthorization cookie from chapter 70 – WITHOUT this option, the browser would NOT automatically attach the cookie on a cross-origin request.
Understanding the cleanup function
return () => { eventSource.close(); } is CRITICAL: WITHOUT this cleanup function, EVERY remount of the component (e.g. navigating away and back) would open an ADDITIONAL, NEVER-closed connection – A classic React memory leak that useEffect's cleanup mechanism exists EXACTLY for THIS case.
Using the hook in the project list
// ProjectListPage.tsx - addition
import { useMercure } from '../hooks/useMercure';
function ProjectListPage() {
useMercure('https://localhost/api/projects/{id}', ['projects']);
const { data, isPending, isError, error } = useProjects();
// ...
}The WILDCARD topic from chapter 69 ({id} instead of a concrete ID) ensures that CHANGES to ANY project INVALIDATE the ['projects'] cache entry – if ANOTHER user changes ONE project, the list updates for US AUTOMATICALLY, WITHOUT reloading the page.
Testing the complete behavior
- Open the project list in TWO browser tabs (or with two different users).
- Edit a project in tab 1 (
PATCH, EXACTLY as in chapter 53). - Tab 2 updates IMMEDIATELY, WITHOUT anything being clicked THERE.
Tipp: THIS hook is the FRONTEND counterpart to EVERYTHING built in the backend since chapter 67 – A GOOD moment to MENTALLY retrace the chain FROM mercure: true (chapter 68) TO this useEffect ONCE MORE, BEFORE moving on to the FULL CRUD UI in block 10.