EdgeStream
Cleanup and Lifecycle
EdgeStream React hooks are designed around React's mount/unmount lifecycle. useSubscription creates a subscription on mount and unsubscribes on unmount — automatically. No manual cleanup is needed when using the provided hooks.
What Happens on Mount
// When a component using useSubscription('bas', 'workflow.*', handler) mounts:
//
// 1. useEffect runs
// 2. client.server('bas') is retrieved
// 3. client.subscribe('bas', 'workflow.*', handler) is called
// → SubscriptionManager registers handler with ID 'sub-xxxx'
// 4. setIsSubscribed(true)
// 5. The subscription ID is stored in unsubscribeRef
What Happens on Unmount
// When the component unmounts:
//
// 1. useEffect cleanup function runs
// 2. unsubscribeRef.current() is called
// → subscription.unsubscribe()
// → SubscriptionManager removes the handler from its Map
// 3. setIsSubscribed(false)
//
// The handler is GONE — no more messages will reach this component
// even if the transport continues receiving them
Cleanup on Topic or ServerId Change
// If serverId or topic changes (e.g., navigating to a different workflow):
//
// React calls the cleanup function for the OLD effect first:
// → old subscription.unsubscribe()
// Then runs the new effect:
// → new subscription for the new serverId/topic is created
//
// There is never a gap where both subscriptions are active simultaneously
function WorkflowDetail({ workflowId }: { workflowId: string }) {
const handler = useCallback((envelope) => {
// only called for messages matching the CURRENT workflowId
handleEvent(envelope);
}, [workflowId]);
useSubscription('bas', `workflow.${workflowId}.*`, handler);
// Old subscription cleaned up automatically when workflowId changes
}
Preventing State Updates After Unmount
// If the handler uses setState and the component unmounts while
// an async operation is in flight, React will warn about state updates
// after unmount. Guard with a ref:
function AsyncSubscriber() {
const isMountedRef = useRef(true);
const [data, setData] = useState(null);
useEffect(() => {
return () => { isMountedRef.current = false; };
}, []);
const handler = useCallback(async (envelope) => {
const result = await fetchDetails(envelope.body.id);
if (isMountedRef.current) { // guard before setState
setData(result);
}
}, []);
useSubscription('bas', 'workflow.*', handler);
return <div>{JSON.stringify(data)}</div>;
}
React StrictMode Double-Mount
// In React 18 StrictMode (development), effects run twice:
// mount → unmount → mount
//
// useSubscription handles this correctly because unsubscribeRef
// is always cleared in the cleanup function before re-subscribing.
// You will see two subscribe() + unsubscribe() cycles in dev,
// but only one active subscription at runtime.
Provider Lifecycle
| Event | Provider Action |
|---|---|
| Provider mounts | Calls edgeStreamClient.start(), subscribes to stream:started/stopped/error |
| Provider unmounts | Sets isMounted=false, unsubscribes from stream events. Does NOT call stop() |
| Child unmounts | useSubscription cleanup removes subscription from SubscriptionManager |
| stream:stopped fires | Provider sets connectionStatus = 'disconnected' |
| stream:error fires | Provider sets connectionStatus = 'error', calls onError prop |
Transport Is Not Stopped on Provider Unmount
EdgeStreamProvider deliberately does not call edgeStreamClient.stop() on unmount. The transport manages its own lifecycle. Call stream.stop() explicitly in your application teardown logic (e.g., window.addEventListener('beforeunload', ...)).