# How Meetily's Sidebar Context Manages Global State in React

> Discover how Meetily's Sidebar Context manages global state in React, centralizing UI data like meetings and configuration without prop drilling for efficient frontend development.

- Repository: [Zackriya Solutions/meetily](https://github.com/Zackriya-Solutions/meetily)
- Tags: how-to-guide
- Published: 2026-07-20

---

**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](https://github.com/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`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src/components/Sidebar/SidebarProvider.tsx). Here, the context is instantiated with a strict TypeScript type to enforce null safety:

```typescript
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:

```typescript
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`](https://github.com/Zackriya-Solutions/meetily/blob/main/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`](https://github.com/Zackriya-Solutions/meetily/blob/main/SidebarProvider.tsx), the provider calls:

```typescript
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`](https://github.com/Zackriya-Solutions/meetily/blob/main/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`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src/app/page.tsx)), state is globally available without prop drilling.

### Example: Collapsing the Sidebar

```tsx
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

```tsx
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

```tsx
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`](https://github.com/Zackriya-Solutions/meetily/blob/main/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`](https://github.com/Zackriya-Solutions/meetily/blob/main/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`](https://github.com/Zackriya-Solutions/meetily/blob/main/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.