INTEGRITY Cloudflare Docs

State Management

Prerequisites

This page builds upon the Basic Implementation Guide. Make sure you've read those first.

The code examples on this page assume you've already imported the necessary packages and initialized the SDK.

How UI Kit Components Communicate

The UI Kit components are able to understand and synchronize with each other because they are nested under the RtkMeeting component. The RtkMeeting component acts as the central coordinator that ensures all components under it stay in sync when it comes to meeting state, participant updates, and other real-time changes.

The UI Kit components are able to understand and synchronize with each other because they are nested under the rtk-meeting component. The rtk-meeting component acts as the central coordinator that ensures all components under it stay in sync when it comes to meeting state, participant updates, and other real-time changes.

The UI Kit components are able to understand and synchronize with each other because they are nested under the rtk-meeting component. The rtk-meeting component acts as the central coordinator that ensures all components under it stay in sync when it comes to meeting state, participant updates, and other real-time changes.

The Android UI Kit manages component communication internally. When you build the UI Kit using RealtimeKitUIBuilder, it creates and coordinates all the necessary UI components. To observe meeting state changes from your application, attach event listeners to the Core SDK's meeting object.

The iOS UI Kit manages component communication internally through the RealtimeKitUI module. To observe meeting state changes from your application, implement event listener protocols and register them on the Core SDK's meeting object.

The React Native UI Kit components communicate and synchronize with each other because they are nested under the RtkMeeting component, wrapped in RealtimeKitProvider and RtkUIProvider. To observe state changes, use hooks from the Core SDK such as useRealtimeKitSelector.

Here's an example of how state synchronization works when opening the participants sidebar:

flowchart LR
    accTitle: Sidebar State Synchronization Example
    accDescr: Example showing how clicking participants toggle updates sidebar through meeting coordination

    Toggle["👤 ParticipantsToggle<br/>(User clicks)"]
    Meeting["Meeting Component<br/>(State Coordinator)"]
    Sidebar["Sidebar<br/>(Opens/Closes)"]
    App["Your App<br/>(Gets notified)"]

    Toggle -->|"emits rtkStateUpdate<br/>{activeSidebar: true,<br/>sidebar: 'participants'}"|Meeting
    Meeting -->|"propagates state"|Sidebar
    Meeting -->|"emits rtkStatesUpdate"|App

    style Meeting fill:#F48120,stroke:#333,stroke-width:2px,color:#fff
    style App fill:#0051C3,stroke:#333,stroke-width:2px,color:#fff

State Flow

  1. Child components emit state updates: When any UI component needs to update state, it emits a rtkStateUpdate event
  2. Meeting component listens and coordinates: The meeting component listens to all these state update events from its children
  3. State propagation: The meeting component propagates the updated state to all other child components to keep them synchronized
  4. External notification: The meeting component also emits a rtkStatesUpdate event that your application can listen to for updating your custom UI or performing actions based on state changes
  1. UI Kit manages internal state: The UI Kit handles all component communication and state synchronization internally
  2. Your app registers event listeners: You attach event listeners (such as RtkMeetingRoomEventListener and RtkSelfEventListener) to the Core SDK's meeting object
  3. Callbacks fire on state changes: When the meeting state changes (for example, a participant joins or audio is toggled), the corresponding listener callback is invoked
  4. You update your UI: Use the callback data to update your application's UI or trigger other actions
  1. UI Kit manages internal state: The RtkMeeting component handles all internal component communication and state synchronization
  2. Your app observes state via hooks: Use useRealtimeKitSelector to select specific meeting properties and re-render when they change
  3. React re-renders on changes: When the selected value changes, React automatically re-renders the component with the new state
  4. You update your UI: Use the observed state values to conditionally render UI elements or trigger side effects

Listening to State Updates

To build custom UI or perform actions based on meeting state changes, you need to observe state updates from the UI Kit.

Listen to the rtkStatesUpdate event emitted by the meeting component. This event provides you with the current state of the UI Kit, including sidebar state, screen sharing status, view type, and more.

Attach event listeners to the Core SDK's meeting object to observe meeting state changes. The mobile UI Kit handles its own internal state, and your app interacts with the underlying meeting object directly.

Use the useRealtimeKitSelector hook from @cloudflare/realtimekit-react-native to observe specific properties on the meeting object. This hook re-renders your component whenever the selected value changes, similar to how selectors work in state management libraries.

Example Code

For React, you can use the onRtkStatesUpdate prop on the RtkMeeting component to listen for state updates.

import {
	RealtimeKitProvider,
	useRealtimeKitClient,
} from "@cloudflare/realtimekit-react";
import { RtkMeeting } from "@cloudflare/realtimekit-react-ui";
import { useEffect, useState } from "react";

function App() {
	const [meeting, initMeeting] = useRealtimeKitClient();
	const [authToken, setAuthToken] = useState("<participant_auth_token>");
	const [states, setStates] = useState({});

	useEffect(() => {
		if (authToken) {
			initMeeting({
				authToken: authToken,
			});
		}
	}, [authToken]);

	return (
		<RealtimeKitProvider value={meeting}>
			<RtkMeeting
				showSetupScreen={true}
				meeting={meeting}
				onRtkStatesUpdate={(e) => {
					// Update states when rtk-meeting emits state updates
					setStates(e.detail);

					// Example: Access various state properties
					console.log("Meeting state:", e.detail.meeting); // 'idle', 'setup', 'joined', 'ended', 'waiting'
					console.log("Is sidebar active:", e.detail.activeSidebar);
					console.log("Current sidebar section:", e.detail.sidebar);
					console.log("Is screen sharing:", e.detail.activeScreenShare);
				}}
			/>

			{/* Use states to build custom UI */}
			<div className="custom-ui">
				<p>Meeting State: {states.meeting}</p>
				<p>Sidebar Open: {states.activeSidebar ? "Yes" : "No"}</p>
			</div>
		</RealtimeKitProvider>
	);
}

Alternative: Using Refs (Multiple Meetings)

If you're building an experience with multiple meetings on the same page or back-to-back meetings, using refs is recommended to avoid state conflicts between different meeting instances:

import {
	RealtimeKitProvider,
	useRealtimeKitClient,
} from "@cloudflare/realtimekit-react";
import { RtkMeeting } from "@cloudflare/realtimekit-react-ui";
import { useEffect, useState, useRef } from "react";

function App() {
	const [meeting, initMeeting] = useRealtimeKitClient();
	const [authToken, setAuthToken] = useState("<participant_auth_token>");
	const [states, setStates] = useState({});
	const meetingRef = useRef(null);

	useEffect(() => {
		if (authToken) {
			initMeeting({
				authToken: authToken,
			});
		}
	}, [authToken]);

	useEffect(() => {
		if (!meetingRef.current) return;

		const handleStatesUpdate = (e) => {
			setStates(e.detail);
			console.log("Meeting state:", e.detail.meeting);
			console.log("Is sidebar active:", e.detail.activeSidebar);
		};

		// Add event listener via ref
		meetingRef.current.addEventListener("rtkStatesUpdate", handleStatesUpdate);

		// Cleanup listener when component unmounts or meeting changes
		return () => {
			meetingRef.current?.removeEventListener(
				"rtkStatesUpdate",
				handleStatesUpdate,
			);
		};
	}, [meetingRef.current]);

	return (
		<RealtimeKitProvider value={meeting}>
			<RtkMeeting ref={meetingRef} showSetupScreen={true} meeting={meeting} />

			{/* Use states to build custom UI */}
			<div className="custom-ui">
				<p>Meeting State: {states.meeting}</p>
				<p>Sidebar Open: {states.activeSidebar ? "Yes" : "No"}</p>
			</div>
		</RealtimeKitProvider>
	);
}

For Web Components, you need to add an event listener to the rtk-meeting component to listen for rtkStatesUpdate events.

<body>
	<rtk-meeting id="meeting-component"></rtk-meeting>
</body>
<script type="module">
	import RealtimeKitClient from "https://cdn.jsdelivr.net/npm/@cloudflare/realtimekit@latest/dist/index.es.js";

	const meeting = await RealtimeKitClient.init({
		authToken: "<participant_auth_token>",
	});

	// Add <rtk-meeting id="meeting-component" /> to your HTML, otherwise you will get error
	const meetingComponent = document.querySelector("#meeting-component");

	// Listen for state updates from rtk-meeting
	meetingComponent.addEventListener("rtkStatesUpdate", (event) => {
		console.log("RTK states updated:", event.detail);

		// Store states to update your custom UI
		const states = event.detail;

		// Example: Access various state properties
		console.log("Meeting state:", states.meeting); // 'idle', 'setup', 'joined', 'ended', 'waiting'
		console.log("Is sidebar active:", states.activeSidebar);
		console.log("Current sidebar section:", states.sidebar); // 'chat', 'participants', 'polls', etc.
		console.log("Is screen sharing:", states.activeScreenShare);

		// Update your custom UI based on states
		// For example: Show/hide elements based on meeting state
		if (states.meeting === "joined") {
			// Show meeting controls
		}
	});

	meetingComponent.showSetupScreen = true;
	meetingComponent.meeting = meeting;
</script>

For Angular, you need to add an event listener to the rtk-meeting component to listen for rtkStatesUpdate events.

meeting.component.ts
import {
	Component,
	ElementRef,
	OnInit,
	OnDestroy,
	ViewChild,
} from "@angular/core";

@Component({
	selector: "app-meeting",
	template: `
		<rtk-meeting #meetingComponent id="meeting-component"></rtk-meeting>

		<!-- Use states to build custom UI -->
		<div class="custom-ui" *ngIf="states">
			<p>Meeting State: {{ states.meeting }}</p>
			<p>Sidebar Open: {{ states.activeSidebar ? "Yes" : "No" }}</p>
			<div *ngIf="states.meeting === 'joined'" class="meeting-controls">
				<!-- Show meeting controls when joined -->
				<p>Meeting controls would go here</p>
			</div>
		</div>
	`,
	styleUrls: ["./meeting.component.css"],
})
export class MeetingComponent implements OnInit, OnDestroy {
	@ViewChild("meetingComponent", { static: true }) meetingElement!: ElementRef;

	meeting: any;
	states: any = {};
	private authToken = "<participant_auth_token>";
	private stateUpdateListener?: (event: any) => void;

	async ngOnInit() {
		// Import RealtimeKit client dynamically
		const RealtimeKitClient = await import(
			"https://cdn.jsdelivr.net/npm/@cloudflare/realtimekit@latest/dist/index.es.js"
		);

		// Initialize the meeting
		this.meeting = await RealtimeKitClient.default.init({
			authToken: this.authToken,
		});

		// Set up the meeting component
		const meetingComponent = this.meetingElement.nativeElement;

		// Create the event listener
		this.stateUpdateListener = (event: any) => {
			console.log("RTK states updated:", event.detail);

			// Store states to update your custom UI
			this.states = event.detail;

			// Example: Access various state properties
			console.log("Meeting state:", this.states.meeting); // 'idle', 'setup', 'joined', 'ended', 'waiting'
			console.log("Is sidebar active:", this.states.activeSidebar);
			console.log("Current sidebar section:", this.states.sidebar); // 'chat', 'participants', 'polls', etc.
			console.log("Is screen sharing:", this.states.activeScreenShare);

			// Update your custom UI based on states
			// For example: Show/hide elements based on meeting state
			if (this.states.meeting === "joined") {
				// Show meeting controls
				console.log("Meeting joined - showing controls");
			}
		};

		// Listen for state updates from rtk-meeting
		meetingComponent.addEventListener(
			"rtkStatesUpdate",
			this.stateUpdateListener,
		);

		// Configure the meeting component
		meetingComponent.showSetupScreen = true;
		meetingComponent.meeting = this.meeting;
	}

	ngOnDestroy() {
		// Clean up event listener when component is destroyed
		if (this.stateUpdateListener && this.meetingElement) {
			this.meetingElement.nativeElement.removeEventListener(
				"rtkStatesUpdate",
				this.stateUpdateListener,
			);
		}
	}
}

For Android, attach event listeners to the meeting object to observe state changes. Use RtkMeetingRoomEventListener for meeting lifecycle events and RtkSelfEventListener for local participant state changes.

import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity

class MeetingActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // After initializing the meeting and UI Kit (see Getting Started guide),
        // add event listeners to observe state changes.

        // Listen for meeting room state changes
        meeting.addMeetingRoomEventListener(object : RtkMeetingRoomEventListener {
            override fun onMeetingRoomJoinStarted() {
                Log.d("Meeting", "Join started")
            }

            override fun onMeetingRoomJoinCompleted(meeting: RealtimeKitClient) {
                Log.d("Meeting", "Joined the meeting")
                // Update UI to show meeting controls
            }

            override fun onMeetingRoomJoinFailed(exception: Exception) {
                Log.e("Meeting", "Join failed: ${exception.message}")
            }

            override fun onMeetingRoomLeaveStarted() {
                Log.d("Meeting", "Leave started")
            }

            override fun onMeetingRoomLeft() {
                Log.d("Meeting", "Left the meeting")
            }

            override fun onMeetingEnded() {
                Log.d("Meeting", "Meeting ended for all participants")
            }

            override fun onActiveTabUpdate(activeTab: ActiveTab) {
                Log.d("Meeting", "Active tab changed: $activeTab")
            }
        })

        // Listen for local participant state changes
        meeting.addSelfEventListener(object : RtkSelfEventListener {
            override fun onAudioUpdate(isEnabled: Boolean) {
                Log.d("Meeting", "Audio: ${if (isEnabled) "on" else "off"}")
            }

            override fun onVideoUpdate(isEnabled: Boolean) {
                Log.d("Meeting", "Video: ${if (isEnabled) "on" else "off"}")
            }

            override fun onRemovedFromMeeting() {
                Log.d("Meeting", "Removed from meeting by host")
            }
        })
    }
}

For iOS, implement event listener protocols and register them on the meeting object. Use RtkMeetingRoomEventListener for meeting lifecycle events and RtkSelfEventListener for local participant state changes.

// Listen for meeting room state changes
extension MeetingViewModel: RtkMeetingRoomEventListener {
    func onMeetingRoomJoinCompleted(meeting: RealtimeKitClient) {
        // Successfully joined the meeting (equivalent to 'joined' state)
    }

    func onMeetingRoomLeft() {
        // Successfully left the meeting
    }

    func onMeetingEnded() {
        // The meeting has ended for all participants (equivalent to 'ended' state)
    }

    func onActiveTabUpdate(activeTab: ActiveTab) {
        // Active tab changed (e.g., chat, polls, participants)
        // Use this to sync your custom UI with the active sidebar section
    }
}

// Listen for local participant state changes
extension MeetingViewModel: RtkSelfEventListener {
    func onAudioUpdate(isEnabled: Bool) {
        // Audio toggled on/off
    }

    func onVideoUpdate(isEnabled: Bool) {
        // Video toggled on/off
    }

    func onRemovedFromMeeting() {
        // Local user was removed from the meeting by host
    }
}

// Register the listeners
meeting.addMeetingRoomEventListener(meetingRoomEventListener: self)
meeting.addSelfEventListener(selfEventListener: self)

For React Native, use the useRealtimeKitSelector hook to observe specific properties on the meeting object. This pattern is similar to the web Core SDK.

import { useEffect } from "react";
import { View, Text } from "react-native";
import {
	RealtimeKitProvider,
	useRealtimeKitClient,
	useRealtimeKitMeeting,
	useRealtimeKitSelector,
} from "@cloudflare/realtimekit-react-native";
import {
	RtkUIProvider,
	RtkMeeting,
} from "@cloudflare/realtimekit-react-native-ui";

function App() {
	const [meeting, initMeeting] = useRealtimeKitClient();

	useEffect(() => {
		initMeeting({
			authToken: "<participant_auth_token>",
			defaults: { audio: true, video: true },
		});
	}, []);

	return (
		<RealtimeKitProvider value={meeting}>
			<RtkUIProvider>
				<MeetingWithState />
			</RtkUIProvider>
		</RealtimeKitProvider>
	);
}

function MeetingWithState() {
	const { meeting } = useRealtimeKitMeeting();

	// Use selectors to observe meeting state
	const roomState = useRealtimeKitSelector((m) => m.self.roomState);
	const audioEnabled = useRealtimeKitSelector((m) => m.self.audioEnabled);
	const videoEnabled = useRealtimeKitSelector((m) => m.self.videoEnabled);

	useEffect(() => {
		console.log("Room state:", roomState);
		console.log("Audio:", audioEnabled);
		console.log("Video:", videoEnabled);
	}, [roomState, audioEnabled, videoEnabled]);

	return (
		<View>
			{meeting && <RtkMeeting meeting={meeting} showSetupScreen={true} />}

			{/* Use state to build custom UI */}
			<View>
				<Text>Room State: {roomState}</Text>
				<Text>Audio: {audioEnabled ? "On" : "Off"}</Text>
				<Text>Video: {videoEnabled ? "On" : "Off"}</Text>
			</View>
		</View>
	);
}

Alternative: Using Event Listeners

You can also use event-based listeners for more fine-grained control, similar to the web Core SDK:

import { useEffect } from "react";

function MeetingEvents() {
	const { meeting } = useRealtimeKitMeeting();

	useEffect(() => {
		if (!meeting) return;

		const handleRoomJoined = () => {
			console.log("Successfully joined the meeting");
		};

		const handleRoomLeft = ({ state }) => {
			if (state === "ended") {
				console.log("Meeting ended");
			}
		};

		meeting.self.on("roomJoined", handleRoomJoined);
		meeting.self.on("roomLeft", handleRoomLeft);

		return () => {
			meeting.self.removeListener("roomJoined", handleRoomJoined);
			meeting.self.removeListener("roomLeft", handleRoomLeft);
		};
	}, [meeting]);

	return null;
}

State Properties

The rtkStatesUpdate event provides detailed information about the UI Kit's internal state. Key properties include:

On mobile platforms, state is observed through Core SDK event listeners rather than a single state object. The key event listeners and their callbacks include:

RtkMeetingRoomEventListener - Meeting lifecycle:

RtkSelfEventListener - Local participant:

RtkParticipantsEventListener - Remote participants:

On React Native, use the useRealtimeKitSelector hook to observe specific properties on the meeting object. Key properties include:

Best Practices