Portal Community

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

EventProvider Action
Provider mountsCalls edgeStreamClient.start(), subscribes to stream:started/stopped/error
Provider unmountsSets isMounted=false, unsubscribes from stream events. Does NOT call stop()
Child unmountsuseSubscription cleanup removes subscription from SubscriptionManager
stream:stopped firesProvider sets connectionStatus = 'disconnected'
stream:error firesProvider 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', ...)).