# How Meetily's Sidebar Context Pattern Works for State Management

> Learn how Meetily's Sidebar Context pattern simplifies state management. Access UI-wide data like meeting selection and recording controls easily with the useSidebar hook. Avoid prop drilling.

- Repository: [Zackriya Solutions/meetily](https://github.com/Zackriya-Solutions/meetily)
- Tags: deep-dive
- Published: 2026-07-15

---

**TLDR:** Meetily uses a React Context pattern through a `SidebarProvider` component that centralizes UI-wide state—including meeting selection, collapse status, recording controls, and transcript search—making it accessible to any descendant component via the `useSidebar()` hook without prop drilling.

Meetily is a desktop meeting application built with React and Tauri that requires complex UI state coordination between the sidebar navigation and main content areas. The application solves this challenge using a dedicated Sidebar Context pattern that encapsulates all sidebar-related state and business logic in a single provider component. This architecture ensures consistent state management across the frontend while maintaining clean separation between UI components and backend communication.

## Core Architecture of the Sidebar Context

### Context Definition and Type Safety

The pattern begins in [`frontend/src/components/Sidebar/SidebarProvider.tsx`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src/components/Sidebar/SidebarProvider.tsx) with `createContext<SidebarContextType | null>(null)`, establishing a strongly-typed contract for all shared state. This approach ensures TypeScript enforcement throughout the component tree while providing a null fallback for safety checks.

### Provider Component Structure

The `SidebarProvider` wraps the application and initializes state using `useState` for values like `serverAddress`, `transcriptServerAddress`, `meetings`, `currentMeeting`, and `isCollapsed`. It memoizes complex interactions through `useCallback` and manages side effects via `useEffect`, ensuring that derived state like `sidebarItems` updates efficiently when dependencies change.

## State Initialization and Side Effects

### Server Configuration Setup

When the provider mounts, it initializes `serverAddress` and `transcriptServerAddress` state variables. These URLs configure all Tauri-invoked backend calls, ensuring the sidebar communicates with the correct Rust backend endpoints for meeting data and transcript processing.

### Meeting Data Synchronization

The provider fetches the meeting list using the Tauri command `api_get_meetings` whenever `serverAddress` changes. The results populate both the `meetings` state and drive the `sidebarItems` tree structure displayed in the navigation panel, ensuring the UI remains synchronized with the backend data source.

### Routing Integration

The context monitors the Next.js pathname to maintain UI consistency. When the pathname equals `/`, the provider resets `currentMeeting` to the "+ New Call" placeholder and rebuilds `sidebarItems` accordingly, creating a seamless navigation experience between the home view and specific meeting details.

## Advanced State Management Mechanisms

### Recording State Coordination

Rather than duplicating recording state, the provider consumes `useRecordingState()` from [`frontend/src/contexts/RecordingStateContext.tsx`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src/contexts/RecordingStateContext.tsx). This ensures a single source of truth for the `isRecording` flag. The `handleRecordingToggle()` function dispatches custom events or navigates to the home page to synchronize UI and backend states, while [`frontend/src/lib/analytics.ts`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src/lib/analytics.ts) handles telemetry for these actions.

### Summary Polling with Cleanup

For asynchronous summary generation, the provider implements `startSummaryPolling()` and `stopSummaryPolling()` methods. These store interval IDs in a `Map<string, NodeJS.Timeout>`, enabling per-meeting polling control and proper cleanup on component unmount to prevent memory leaks from orphaned timers.

### Transcript Search Implementation

The `searchTranscripts()` function exposes backend search capabilities to UI components, managing `isSearching` and `searchResults` states during the operation. This allows any component to initiate searches without implementing duplicate fetch logic.

## Consuming the Sidebar Context

Components access this state through the `useSidebar()` hook, which safely consumes the context and throws an error if called outside the provider boundary.

```typescript
// Consume the context in a sidebar component
import { useSidebar } from '@/components/Sidebar/SidebarProvider';

function MeetingList() {
  const {
    meetings,
    currentMeeting,
    setCurrentMeeting,
    toggleCollapse,
    isCollapsed,
    handleRecordingToggle,
  } = useSidebar();

  return (
    <div>
      <button onClick={toggleCollapse}>
        {isCollapsed ? 'Expand' : 'Collapse'}
      </button>

      <ul>
        {meetings.map(m => (
          <li
            key={m.id}
            style={{
              fontWeight: currentMeeting?.id === m.id ? 'bold' : 'normal',
            }}
            onClick={() => setCurrentMeeting(m)}
          >
            {m.title}
          </li>
        ))}
      </ul>

      <button onClick={handleRecordingToggle}>Start / Stop</button>
    </div>
  );
}

```

```typescript
// Trigger a transcript search from anywhere in the app
import { useSidebar } from '@/components/Sidebar/SidebarProvider';

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

  const onSubmit = async (e) => {
    e.preventDefault();
    await searchTranscripts(query);
  };

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

      {isSearching && <p>Searching…</p>}
      <ul>
        {searchResults.map(r => (
          <li key={r.id}>
            <strong>{r.title}</strong> – {r.matchContext}
          </li>
        ))}
      </ul>
    </form>
  );
}

```

```typescript
// Start and stop summary polling for a specific meeting
import { useSidebar } from '@/components/Sidebar/SidebarProvider';

function SummaryButton({ meetingId }: { meetingId: string }) {
  const { startSummaryPolling, stopSummaryPolling } = useSidebar();

  const start = () => {
    startSummaryPolling(meetingId, 'process-123', (result) => {
      console.log('Summary update:', result);
    });
  };

  const stop = () => stopSummaryPolling(meetingId);

  return (
    <>
      <button onClick={start}>Start Summary</button>
      <button onClick={stop}>Stop Summary</button>
    </>
  );
}

```

## Key Files and Responsibilities

| File | Role |
|------|------|
| [`frontend/src/components/Sidebar/SidebarProvider.tsx`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src/components/Sidebar/SidebarProvider.tsx) | Defines the `SidebarContext`, provider component, state hooks, backend invocations, and polling logic. |
| [`frontend/src/contexts/RecordingStateContext.tsx`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src/contexts/RecordingStateContext.tsx) | Supplies the global `isRecording` flag used by the sidebar for toggle logic. |
| [`frontend/src/lib/analytics.ts`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src/lib/analytics.ts) | Handles telemetry for sidebar actions (e.g., start/stop recording). |
| [`frontend/src/app/page.tsx`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src/app/page.tsx) | Consumes the sidebar context to display meeting details and trigger recordings. |

## Summary

- The **Sidebar Context pattern** centralizes all sidebar-related state in [`frontend/src/components/Sidebar/SidebarProvider.tsx`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src/components/Sidebar/SidebarProvider.tsx), eliminating prop drilling across the component tree.
- **State initialization** occurs through `useState` and `useEffect`, fetching meetings via Tauri's `api_get_meetings` command whenever server configuration changes.
- **Cross-context integration** with `useRecordingState()` ensures consistent recording status across the application without state duplication.
- **Memory management** is handled through a `Map<string, NodeJS.Timeout>` for polling intervals with explicit cleanup via `stopSummaryPolling()` and unmount effects.
- **Component access** is standardized through the `useSidebar()` hook, which provides type-safe access to state and actions while enforcing provider boundary constraints.

## Frequently Asked Questions

### What is the Sidebar Context pattern in Meetily?

Meetily's Sidebar Context pattern is a React Context implementation that encapsulates all sidebar-related state and logic within a `SidebarProvider` component. It provides a centralized store for meeting data, UI collapse states, recording controls, and search functionality, accessible via the `useSidebar()` hook throughout the component tree.

### How does the sidebar handle recording state without duplication?

The sidebar delegates recording state to a separate `RecordingStateContext` by calling `useRecordingState()` from [`frontend/src/contexts/RecordingStateContext.tsx`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src/contexts/RecordingStateContext.tsx). This prevents state duplication and ensures the `isRecording` flag remains synchronized across the entire application, with the sidebar's `handleRecordingToggle()` function coordinating UI updates and backend dispatch.

### What mechanism prevents memory leaks during summary polling?

The provider maintains a `Map<string, NodeJS.Timeout>` to track active polling intervals per meeting. The `stopSummaryPolling()` method clears specific intervals from this map, while the provider's cleanup logic ensures all intervals are terminated when the component unmounts, preventing memory leaks from orphaned timers.

### How do components trigger a refresh of the meeting list?

Any component can call `refetchMeetings()` (exposed as `fetchMeetings` in the context value) to trigger a fresh fetch from the Rust backend via the `api_get_meetings` Tauri command. This function is memoized with `useCallback` to maintain stable references across renders, allowing components like [`frontend/src/app/page.tsx`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src/app/page.tsx) to refresh data on demand.