Skip to content

Instantly share code, notes, and snippets.

@brettinternet
Created February 19, 2025 03:11
Show Gist options
  • Save brettinternet/bb3ed02e82ade9d033038fb50db2a0c3 to your computer and use it in GitHub Desktop.
Save brettinternet/bb3ed02e82ade9d033038fb50db2a0c3 to your computer and use it in GitHub Desktop.
A React hook that may be spread out across a client app, but ensures a function is called once on first "mount" and then only cleaned up once.
const disconnectListenerCount = new Map<string, number>()
const disconnectListenerCleanup = new Map<string, () => void>()
// We want subscribe and cleanup each to only happen once despite
// This hook being called multiple times across the app in various components.
// So subscribe on the first call and cleanup on the last cleanup call (return from useEffect).
const allowDisconnectListener = (key: string) => {
// Check if first time before incrementing count
const isFirst = !disconnectListenerCount.has(key)
const count = disconnectListenerCount.get(key) ?? 0
disconnectListenerCount.set(key, count + 1)
return isFirst
}
const addDisconnectListenerCleanup = (key: string, cleanup: () => void) => {
disconnectListenerCleanup.set(key, cleanup)
}
// decrement count each time useEffect cleanup is called, cleanup on last call
const maybeCleanupDisconnectListener = (key: string): boolean => {
const count = disconnectListenerCount.get(key) ?? 0
disconnectListenerCount.set(key, count - 1)
const shouldCleanUp = count <= 1
if (shouldCleanUp) {
disconnectListenerCount.delete(key)
const cleanup = disconnectListenerCleanup.get(key)
if (cleanup) {
disconnectListenerCleanup.delete(key)
try {
cleanup()
} catch (_) {}
return true
}
}
return false
}
export const useDisconnectedCallback = (key: string, cb: () => void) => {
const callbackRef = useRef(cb)
useEffect(() => {
callbackRef.current = cb
})
useEffect(() => {
// increment count each time useEffect is called, decrement on cleanup
if (allowDisconnectListener(key)) {
const cancel = subscribe(
EventNames.APP_DISCONNECTED,
callbackRef.current
)
addDisconnectListenerCleanup(key, cancel)
}
return () => {
maybeCleanupDisconnectListener(key)
}
}, [key])
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment