How Meetily's Sidebar Context Manages Global State in React

The Meetily frontend uses a dedicated React context called SidebarContext to centralize UI-wide state—including meeting data, navigation trees, recording status, and backend configuration—making it accessible throughout the component tree without prop drilling.

The open-source meeting assistant Zackriya-Solutions/meetily implements a clean global state management pattern using React's Context API. By isolating sidebar-related state in a dedicated provider, the application ensures consistent UI behavior across navigation, recording lifecycles, and backend interactions.

Context Architecture and Provider Setup

Creating the SidebarContext

The foundation of Meetily's global state lives in frontend/src/components/Sidebar/SidebarProvider.tsx. Here, the context is instantiated with a strict TypeScript type to enforce null safety:

const SidebarContext = createContext<SidebarContextType | null>(null);

This definition appears at line 57 of the provider file. The SidebarContextType interface defines the shape of all state variables and helper functions exposed to consumers.

The useSidebar Hook

To prevent components from accessing the context outside the provider tree, Meetily implements a custom hook that wraps useContext with an explicit guard:

export function useSidebar() {
  const context = useContext(SidebarContext);
  if (context === null) {
    throw new Error('useSidebar must be used within a SidebarProvider');
  }
  return context;
}

This hook is defined at lines 59–65 in SidebarProvider.tsx. It guarantees that any component calling useSidebar() receives a valid context object or fails fast with a descriptive error.

Global State Variables in SidebarProvider

Inside the SidebarProvider component (starting around line 68), React's useState hooks declare the following global state variables:

  • currentMeeting – Tracks the active meeting object or a placeholder for new calls (lines 68–69)
  • isCollapsed – Boolean controlling the sidebar's expanded/collapsed UI state (line 70)
  • meetings – Array of meeting objects fetched from the Tauri backend (line 71)
  • sidebarItems – Computed navigation tree derived from the meetings array, structured as folders and files (lines 72–74)
  • isMeetingActive – Boolean indicating whether a recording is currently in progress (line 73)
  • searchResults and isSearching – State for the transcript search feature (lines 74–75)
  • serverAddress and transcriptServerAddress – Backend URLs persisted for API calls (lines 76–78)
  • activeSummaryPolls – A Map storing setInterval timers for ongoing summary generation polling (lines 77–78)

These variables compose the global state accessible to any descendant component in the tree.

Cross-Context Integration with Recording State

Rather than duplicating recording logic, Meetily's sidebar context consumes state from a separate RecordingStateContext. At lines 79–80 of SidebarProvider.tsx, the provider calls:

const { isRecording, status } = useRecordingState();

This pattern establishes a single source of truth for recording status while keeping the sidebar context decoupled from direct backend polling. By integrating with RecordingStateContext, the sidebar displays accurate recording indicators without managing the complex state machine itself.

Side Effects and Data Flow

Fetching Meeting Data

The provider memoizes a fetchMeetings function using useCallback (lines 85–103). This function invokes the Tauri backend command api_get_meetings and normalizes the response into the meetings state array. The callback re-executes whenever serverAddress changes, ensuring the UI stays synchronized with backend configuration updates.

Building the Navigation Tree

Two useEffect hooks (lines 32–44) recompute sidebarItems whenever:

  1. The Next.js route changes (pathname dependency)
  2. The raw meetings array updates

This derived state transforms flat meeting data into a hierarchical folder structure displayed in the sidebar navigation panel.

Managing Recording Toggles

The handleRecordingToggle function (lines 45–63) inspects isRecording from the recording context, dispatches custom DOM events for legacy components, and navigates to the home route when appropriate. It also logs interactions via the Analytics utility imported from frontend/src/lib/analytics.ts.

Transcript Search and Summary Polling

  • Search: The searchTranscripts method (lines 66–84) calls invoke('api_search_transcripts') and updates searchResults and isSearching states for the search UI overlay.
  • Polling: startSummaryPolling and stopSummaryPolling (lines 87–88, 115–164) manage per-meeting interval timers stored in the activeSummaryPolls Map, cleaning up intervals on component unmount to prevent memory leaks.

Consuming Sidebar State in Components

Any component can access this global state by importing the useSidebar hook. Because the provider wraps the application entry point (typically in frontend/src/app/page.tsx), state is globally available without prop drilling.

Example: Collapsing the Sidebar

import { useSidebar } from '@/components/Sidebar/SidebarProvider';

export function CollapseButton() {
  const { isCollapsed, toggleCollapse } = useSidebar();

  return (
    <button onClick={toggleCollapse}>
      {isCollapsed ? '▶︎ Expand' : '◀︎ Collapse'}
    </button>
  );
}

Example: Opening a Meeting

import { useSidebar } from '@/components/Sidebar/SidebarProvider';
import { useRouter } from 'next/navigation';

export function MeetingList() {
  const { meetings, setCurrentMeeting } = useSidebar();
  const router = useRouter();

  const openMeeting = (id: string) => {
    setCurrentMeeting({ id, title: meetings.find(m => m.id === id)!.title });
    router.push(`/meeting/${id}`);
  };

  return (
    <ul>
      {meetings.map(m => (
        <li key={m.id} onClick={() => openMeeting(m.id)}>{m.title}</li>
      ))}
    </ul>
  );
}

Example: Searching Transcripts

import { useSidebar } from '@/components/Sidebar/SidebarProvider';
import { useState } from 'react';

export function TranscriptSearch() {
  const { searchTranscripts, isSearching, searchResults } = useSidebar();
  const [query, setQuery] = useState('');

  const submit = (e: React.FormEvent) => {
    e.preventDefault();
    searchTranscripts(query);
  };

  return (
    <form onSubmit={submit}>
      <input value={query} onChange={e => setQuery(e.target.value)} placeholder="Search transcripts…" />
      <button type="submit" disabled={isSearching}>Search</button>

      {isSearching && <p>Searching…</p>}
      {searchResults.map(r => (
        <div key={r.id}>
          <h4>{r.title}</h4>
          <p>{r.matchContext}</p>
        </div>
      ))}
    </form>
  );
}

Summary

  • The SidebarContext in frontend/src/components/Sidebar/SidebarProvider.tsx serves as the central hub for UI-wide state in Meetily's React frontend.
  • useSidebar provides a type-safe hook for consuming components, throwing errors if used outside the provider tree.
  • State includes currentMeeting, meetings, sidebarItems, search results, server addresses, and active polling timers.
  • The sidebar integrates with RecordingStateContext to display recording status without duplicating state logic.
  • Data fetching occurs via memoized callbacks invoking Tauri commands, while side effects handle navigation tree reconstruction and cleanup operations.

Frequently Asked Questions

What is the purpose of the SidebarContext in Meetily?

The SidebarContext encapsulates all global UI state related to the sidebar navigation, meeting management, and backend configuration. It eliminates prop drilling by providing a single source of truth accessible via the useSidebar hook throughout the component tree.

How does the sidebar know if a recording is active?

The sidebar consumes recording status from the RecordingStateContext using the useRecordingState() hook at lines 79–80 of SidebarProvider.tsx. This cross-context pattern ensures the sidebar displays accurate recording indicators while the specialized context handles the complex state machine and backend polling.

Where is the sidebar state initialized in the application?

The SidebarProvider component wraps the application root in frontend/src/app/page.tsx (or a similar entry point), making the context value available globally. The provider initializes state variables using React's useState and populates default server addresses via a mount effect at lines 108–114.

What happens to polling intervals when the sidebar unmounts?

The provider stores active polling timers in the activeSummaryPolls Map and cleans them up in a useEffect return function (lines 115–164). This prevents memory leaks by ensuring all setInterval timers are cleared when the component unmounts or when specific meetings complete their summary generation.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →