♻️(frontend) turn ConnectionObserver hook into a leaf component

Refactor the connection observer, previously a hook consumed inside
the Videoconference component, into a dedicated leaf component in
the tree.

This avoids re-rendering the whole Videoconference component every
time the connection state changes; only the leaf reacts to it.
This commit is contained in:
lebaudantoine
2026-07-14 18:33:50 +02:00
committed by aleb_the_flash
parent e1457604ee
commit f4493d412f
3 changed files with 66 additions and 62 deletions
@@ -1,27 +1,35 @@
import { useConfig } from '@/api/useConfig'
import { useIsAnalyticsEnabled } from '@/features/analytics/hooks/useIsAnalyticsEnabled'
import { import {
useRemoteParticipants, useRemoteParticipants,
useRoomContext, useRoomContext,
} from '@livekit/components-react' } from '@livekit/components-react'
import { useEffect, useRef } from 'react' import { useEffect, useRef } from 'react'
import { DisconnectReason, RoomEvent } from 'livekit-client'
import { useIsAnalyticsEnabled } from '@/features/analytics/hooks/useIsAnalyticsEnabled'
import posthog from 'posthog-js'
import { connectionObserverStore } from '@/stores/connectionObserver'
import { useConfig } from '@/api/useConfig'
import { userPreferencesStore } from '@/stores/userPreferences'
import { useSnapshot } from 'valtio' import { useSnapshot } from 'valtio'
import { useIsAdvancedConnectionObserverEnabled } from './useIsAdvancedConnectionObserverEnabled' import { DisconnectReason, RoomEvent } from 'livekit-client'
import { userPreferencesStore } from '@/stores/userPreferences'
import { connectionObserverStore } from '@/stores/connectionObserver'
import posthog from 'posthog-js'
import { useFeatureFlagEnabled } from 'posthog-js/react'
import { isMobileBrowser } from '@livekit/components-core'
import { FeatureFlags } from '@/features/analytics/enums'
const CANDIDATE_POLL_INTERVAL_MS = 5000 const CANDIDATE_POLL_INTERVAL_MS = 5000
export const useConnectionObserver = () => { export const ConnectionObserver = () => {
const room = useRoomContext() const room = useRoomContext()
const connectionStartTimeRef = useRef<number | null>(null) const connectionStartTimeRef = useRef<number | null>(null)
const { data } = useConfig() const { data } = useConfig()
const isAnalyticsEnabled = useIsAnalyticsEnabled() const isAnalyticsEnabled = useIsAnalyticsEnabled()
const featureEnabled = useFeatureFlagEnabled(FeatureFlags.candidatePolling)
const isMobile = isMobileBrowser()
const isAdvancedConnectionObserverEnabled = const isAdvancedConnectionObserverEnabled =
useIsAdvancedConnectionObserverEnabled() !isMobile && isAnalyticsEnabled && featureEnabled
const userPreferencesSnap = useSnapshot(userPreferencesStore) const userPreferencesSnap = useSnapshot(userPreferencesStore)
@@ -232,4 +240,6 @@ export const useConnectionObserver = () => {
connectionStartTimeRef.current = null connectionStartTimeRef.current = null
} }
}, []) }, [])
return null
} }
@@ -1,12 +0,0 @@
import { useFeatureFlagEnabled } from 'posthog-js/react'
import { FeatureFlags } from '@/features/analytics/enums.ts'
import { useIsAnalyticsEnabled } from '@/features/analytics/hooks/useIsAnalyticsEnabled.ts'
import { isMobileBrowser } from '@livekit/components-core'
export const useIsAdvancedConnectionObserverEnabled = () => {
const featureEnabled = useFeatureFlagEnabled(FeatureFlags.candidatePolling)
const isAnalyticsEnabled = useIsAnalyticsEnabled()
const isMobile = isMobileBrowser()
return !isMobile && isAnalyticsEnabled && featureEnabled
}
@@ -10,7 +10,7 @@ import { ControlBar } from './ControlBar/ControlBar'
import { SidePanel } from '../components/SidePanel' import { SidePanel } from '../components/SidePanel'
import { RecordingProvider } from '@/features/recording' import { RecordingProvider } from '@/features/recording'
import { ScreenShareErrorModal } from '../components/ScreenShareErrorModal' import { ScreenShareErrorModal } from '../components/ScreenShareErrorModal'
import { useConnectionObserver } from '../hooks/useConnectionObserver' import { ConnectionObserver } from '../components/ConnectionObserver'
import { useRoomPageTitle } from '../hooks/useRoomPageTitle' import { useRoomPageTitle } from '../hooks/useRoomPageTitle'
import { useNoiseReduction } from '../hooks/useNoiseReduction' import { useNoiseReduction } from '../hooks/useNoiseReduction'
import { useRegisterKeyboardShortcut } from '@/features/shortcuts/useRegisterKeyboardShortcut' import { useRegisterKeyboardShortcut } from '@/features/shortcuts/useRegisterKeyboardShortcut'
@@ -56,7 +56,6 @@ export interface VideoConferenceProps extends React.HTMLAttributes<HTMLDivElemen
export function VideoConference({ ...props }: VideoConferenceProps) { export function VideoConference({ ...props }: VideoConferenceProps) {
const { toggleSettingsDialog } = useSettingsDialog() const { toggleSettingsDialog } = useSettingsDialog()
useConnectionObserver()
useRoomPageTitle() useRoomPageTitle()
useVideoResolutionSubscription() useVideoResolutionSubscription()
useSyncLiveKitMetadata() useSyncLiveKitMetadata()
@@ -75,44 +74,51 @@ export function VideoConference({ ...props }: VideoConferenceProps) {
const [isShareErrorVisible, setIsShareErrorVisible] = useState(false) const [isShareErrorVisible, setIsShareErrorVisible] = useState(false)
return ( return (
<div <>
className="lk-video-conference" <ConnectionObserver />
{...props} <div
style={{ className="lk-video-conference"
overflowX: 'hidden', {...props}
}} style={{
> overflowX: 'hidden',
{isWeb() && ( }}
<> >
<ScreenShareErrorModal {isWeb() && (
isOpen={isShareErrorVisible} <>
onClose={() => setIsShareErrorVisible(false)} <ScreenShareErrorModal
/> isOpen={isShareErrorVisible}
<IsIdleDisconnectModal /> onClose={() => setIsShareErrorVisible(false)}
<PinAnnouncer /> />
<RoomContentArea> <IsIdleDisconnectModal />
{isPictureInPictureOpen ? <PipRoomPlaceholder /> : <StageLayout />} <PinAnnouncer />
</RoomContentArea> <RoomContentArea>
<ControlBar {isPictureInPictureOpen ? (
onDeviceError={(e) => { <PipRoomPlaceholder />
console.error(e) ) : (
if ( <StageLayout />
e.source == Track.Source.ScreenShare && )}
e.error.toString() == </RoomContentArea>
'NotAllowedError: Permission denied by system' <ControlBar
) { onDeviceError={(e) => {
setIsShareErrorVisible(true) console.error(e)
} if (
}} e.source == Track.Source.ScreenShare &&
/> e.error.toString() ==
<SidePanel /> 'NotAllowedError: Permission denied by system'
</> ) {
)} setIsShareErrorVisible(true)
<RoomAudioRenderer /> }
<ConnectionStateToast /> }}
<RecordingProvider /> />
<SettingsDialogProvider /> <SidePanel />
<ReactionPortals /> </>
</div> )}
<RoomAudioRenderer />
<ConnectionStateToast />
<RecordingProvider />
<SettingsDialogProvider />
<ReactionPortals />
</div>
</>
) )
} }