@@ -29,18 +29,13 @@ export function useRealtimeEvents(options: SSEOptions = {}) {
2929 const [ reconnectCount , setReconnectCount ] = useState ( 0 )
3030 const sourceRef = useRef < EventSource | null > ( null )
3131 const reconnectTimer = useRef < number > ( undefined )
32- const backoffRef = useRef ( 1000 ) // start at 1s, max 30s
32+ const backoffRef = useRef ( 1000 )
3333 const eventBufferRef = useRef < BufferedEvent [ ] > ( [ ] )
3434 const hasConnectedOnce = useRef ( false )
3535
36- const flushEventBuffer = useCallback ( ( ) => {
37- const buffer = eventBufferRef . current
38- eventBufferRef . current = [ ]
39- for ( const event of buffer ) {
40- if ( event . type === 'vehicle_update' ) onVehicleUpdate ?.( event . data )
41- else if ( event . type === 'alert' ) onAlert ?.( event . data )
42- }
43- } , [ onVehicleUpdate , onAlert ] )
36+ // Store callbacks in refs to avoid recreating the connection on every render
37+ const cbRefs = useRef ( { onVehicleUpdate, onAlert, onConnected, onDisconnected } )
38+ cbRefs . current = { onVehicleUpdate, onAlert, onConnected, onDisconnected }
4439
4540 const connect = useCallback ( ( ) => {
4641 if ( sourceRef . current ) {
@@ -53,25 +48,28 @@ export function useRealtimeEvents(options: SSEOptions = {}) {
5348 source . addEventListener ( 'connected' , ( e ) => {
5449 setConnected ( true )
5550 setStatus ( 'connected' )
56- backoffRef . current = 1000 // reset backoff on successful connection
51+ backoffRef . current = 1000
5752 if ( hasConnectedOnce . current ) {
5853 setReconnectCount ( c => c + 1 )
5954 }
6055 hasConnectedOnce . current = true
6156 const data = JSON . parse ( e . data )
62- onConnected ?.( data . client_id )
63- // Flush any events buffered during disconnect
64- flushEventBuffer ( )
57+ cbRefs . current . onConnected ?.( data . client_id )
58+ // Flush buffered events
59+ const buffer = eventBufferRef . current
60+ eventBufferRef . current = [ ]
61+ for ( const evt of buffer ) {
62+ if ( evt . type === 'vehicle_update' ) cbRefs . current . onVehicleUpdate ?.( evt . data )
63+ else if ( evt . type === 'alert' ) cbRefs . current . onAlert ?.( evt . data )
64+ }
6565 } )
6666
6767 source . addEventListener ( 'vehicle_update' , ( e ) => {
68- const data = JSON . parse ( e . data )
69- onVehicleUpdate ?.( data )
68+ cbRefs . current . onVehicleUpdate ?.( JSON . parse ( e . data ) )
7069 } )
7170
7271 source . addEventListener ( 'alert' , ( e ) => {
73- const data = JSON . parse ( e . data )
74- onAlert ?.( data )
72+ cbRefs . current . onAlert ?.( JSON . parse ( e . data ) )
7573 } )
7674
7775 source . addEventListener ( 'heartbeat' , ( ) => {
@@ -82,21 +80,18 @@ export function useRealtimeEvents(options: SSEOptions = {}) {
8280 setConnected ( false )
8381 const wasConnected = hasConnectedOnce . current
8482 setStatus ( wasConnected ? 'reconnecting' : 'disconnected' )
85- onDisconnected ?.( )
83+ cbRefs . current . onDisconnected ?.( )
8684 source . close ( )
8785 sourceRef . current = null
88- // Exponential backoff with jitter, capped at 30s
8986 const jitter = Math . random ( ) * 500
9087 const delay = Math . min ( backoffRef . current + jitter , 30_000 )
9188 backoffRef . current = Math . min ( backoffRef . current * 2 , 30_000 )
9289 reconnectTimer . current = window . setTimeout ( connect , delay )
9390 }
94- } , [ onVehicleUpdate , onAlert , onConnected , onDisconnected , flushEventBuffer ] )
91+ } , [ ] ) // no deps β callbacks accessed via stable refs
9592
96- /** Buffer an event to replay after reconnection */
9793 const bufferEvent = useCallback ( ( type : string , data : unknown ) => {
9894 eventBufferRef . current . push ( { type, data, timestamp : Date . now ( ) } )
99- // Keep buffer bounded to prevent memory leaks
10095 if ( eventBufferRef . current . length > 200 ) {
10196 eventBufferRef . current = eventBufferRef . current . slice ( - 100 )
10297 }
0 commit comments