# How to Handle TipTap Editor State Management with React Context in reactjs-tiptap-editor

> Learn to manage TipTap editor state in React using Context. Access and control your editor instance easily with the useEditorInstance hook, avoiding prop-drilling.

- Repository: [Hung Hoang/reactjs-tiptap-editor](https://github.com/hunghg255/reactjs-tiptap-editor)
- Tags: how-to-guide
- Published: 2026-03-03

---

**The reactjs-tiptap-editor package isolates the TipTap editor instance inside a React Context provider so any component can read or control the editor via the `useEditorInstance` hook without prop-drilling.**

Managing editor state across complex React component trees often creates maintenance headaches. The **hunghg255/reactjs-tiptap-editor** repository solves this by implementing a dedicated React Context architecture that wraps the TipTap editor, enabling fully reactive state management and cross-component communication.

## Architecture Overview

The state management system in `reactjs-tiptap-editor` consists of three coordinated pieces that work together to provide editor access throughout your application.

### RichTextProvider

The `RichTextProvider` component serves as the top-level wrapper that creates the context and injects the editor instance. Located in [`src/components/RichTextProvider.tsx`](https://github.com/hunghg255/reactjs-tiptap-editor/blob/main/src/components/RichTextProvider.tsx) (lines 44-45), this provider wraps your app (or a specific part of it) and supplies both the `EditorContext` from `@tiptap/react` and a custom event bus. This is the entry point for all editor state management.

### EditorContext

`EditorContext.Provider` is the official TipTap context that holds the `{ editor }` object. All TipTap-aware hooks—including `useEditorInstance`, `useActive`, and `useAttributes`—read the editor from this context. By storing the editor instance in React Context, state updates automatically propagate when the editor changes (e.g., selection changes, transaction updates), keeping your UI fully reactive.

### ReactBusProvider

`ReactBusProvider` implements a lightweight `mitt`-based event bus for cross-component communication. Found in [`src/components/ReactBus.tsx`](https://github.com/hunghg255/reactjs-tiptap-editor/blob/main/src/components/ReactBus.tsx) (lines 7-8), this provider exposes `emit` and `on` helpers that any component can import via the `useBus` hook. This handles events like opening the slash dialog or emitting toolbar actions without coupling components directly to the editor context.

## Data Flow Implementation

The editor state flows through your application in four distinct steps:

1. **Create the editor** using `useEditor` from `@tiptap/react` in a parent component.
2. **Pass the editor** to `<RichTextProvider editor={editor}>`, which stores it in `EditorContext`.
3. **Access the context**—a `BusContext` (`mitt` instance) is made available through `ReactBusProvider` alongside the editor.
4. **Consume in descendants**—child components call `useEditorInstance()` to invoke TipTap commands or `useBus()` to subscribe to custom events.

Because the editor lives in React Context, you avoid manually forwarding the editor through props while maintaining optimal performance. The provider renders only once, and child components subscribe to specific editor events via TipTap's built-in listeners, preventing unnecessary re-renders.

## Practical Code Examples

### Setting Up the Provider

Wrap your application or editor section with `RichTextProvider` after creating the editor instance with `useEditor`:

```tsx
import { useEditor } from '@tiptap/react';
import { RichTextProvider } from 'reactjs-tiptap-editor';

export default function MyEditorPage() {
  const editor = useEditor({
    extensions: [],        // load any extensions you need
    content: '<p>Hello, world!</p>',
  });

  return (
    <RichTextProvider editor={editor!}>
      {/* All child components now have access to the editor */}
      <MyToolbar />
      <EditorContent />
    </RichTextProvider>
  );
}

```

This setup corresponds to the provider implementation in [`src/components/RichTextProvider.tsx`](https://github.com/hunghg255/reactjs-tiptap-editor/blob/main/src/components/RichTextProvider.tsx) that injects `EditorContext` into your component tree.

### Consuming the Editor Instance

Access the editor in any child component using the `useEditorInstance` hook, which reads from the `EditorContext` created by `RichTextProvider`. This hook lives in [`src/store/editor.ts`](https://github.com/hunghg255/reactjs-tiptap-editor/blob/main/src/store/editor.ts):

```tsx
import { useEditorInstance } from '@/store/editor';
import { Button } from '@/components/ui/button';

export function MyToolbar() {
  const { editor } = useEditorInstance();

  const toggleBold = () => {
    editor?.chain().focus().toggleBold().run();
  };

  return (
    <Button onClick={toggleBold}>Bold</Button>
  );
}

```

### Cross-Component Communication with the Event Bus

For decoupled communication between components (such as opening dialogs or toolbar actions), use the `useBus` hook from `ReactBusProvider`. The bus is provided by `ReactBusProvider` and enables emit/subscribe patterns:

**Emitting events:**

```tsx
import { useBus } from '@/components/ReactBus';
import { Button } from '@/components/ui/button';

export function SlashDialogTrigger() {
  const { emit } = useBus();

  const openSlash = () => {
    emit('openSlashDialog');   // any component listening for this will react
  };

  return (
    <Button onClick={openSlash}>/</Button>
  );
}

```

**Listening for events:**

```tsx
import { useEffect, useState } from 'react';
import { useBus } from '@/components/ReactBus';
import { Dialog } from '@/components/ui/dialog';

export function SlashDialog() {
  const { on, off } = useBus();
  const [open, setOpen] = useState(false);

  useEffect(() => {
    const handler = () => setOpen(true);
    on('openSlashDialog', handler);
    return () => off('openSlashDialog', handler);
  }, [on, off]);

  return <Dialog open={open}>{/* dialog content */}</Dialog>;
}

```

The `on` and `off` methods are simple wrappers around the `mitt` instance, enabling decoupled communication without prop-drilling.

## Key Source Files

Understanding these files helps you extend the editor state management for your own needs:

- **[`src/components/RichTextProvider.tsx`](https://github.com/hunghg255/reactjs-tiptap-editor/blob/main/src/components/RichTextProvider.tsx)** — Main provider that wraps the app, injects `EditorContext` and the custom `ReactBus`.
- **[`src/components/ReactBus.tsx`](https://github.com/hunghg255/reactjs-tiptap-editor/blob/main/src/components/ReactBus.tsx)** — Implements the `mitt` event bus and exports `BusContext`, `emit`, `useBus`, and `ReactBusProvider`.
- **[`src/store/editor.ts`](https://github.com/hunghg255/reactjs-tiptap-editor/blob/main/src/store/editor.ts)** — Re-exports the `EditorContext` and provides the `useEditorInstance` hook used throughout the library.
- **[`src/hooks/useExtension.tsx`](https://github.com/hunghg255/reactjs-tiptap-editor/blob/main/src/hooks/useExtension.tsx)** — Example hook that accesses the editor via `useEditorInstance` and registers extension side-effects.
- **[`src/components/SlashDialogTrigger/SlashDialogTrigger.tsx`](https://github.com/hunghg255/reactjs-tiptap-editor/blob/main/src/components/SlashDialogTrigger/SlashDialogTrigger.tsx)** — UI component that emits custom events to open the slash dialog, demonstrating practical bus usage.

## Summary

- **React Context isolation**: The `RichTextProvider` in [`src/components/RichTextProvider.tsx`](https://github.com/hunghg255/reactjs-tiptap-editor/blob/main/src/components/RichTextProvider.tsx) creates a centralized editor state that eliminates prop-drilling.
- **Reactive updates**: Storing the editor in `EditorContext` ensures automatic UI updates when editor state changes.
- **Decoupled communication**: The `ReactBusProvider` ([`src/components/ReactBus.tsx`](https://github.com/hunghg255/reactjs-tiptap-editor/blob/main/src/components/ReactBus.tsx)) adds a `mitt`-based event bus for cross-component events without direct coupling.
- **Hook-based access**: Use `useEditorInstance()` from [`src/store/editor.ts`](https://github.com/hunghg255/reactjs-tiptap-editor/blob/main/src/store/editor.ts) to execute commands, and `useBus()` for custom event emission.
- **Performance optimized**: The architecture minimizes re-renders by leveraging TipTap's built-in listeners rather than React state for editor content changes.

## Frequently Asked Questions

### What is the role of RichTextProvider in reactjs-tiptap-editor?

`RichTextProvider` is the top-level component in [`src/components/RichTextProvider.tsx`](https://github.com/hunghg255/reactjs-tiptap-editor/blob/main/src/components/RichTextProvider.tsx) that instantiates the React Context structure. It accepts an `editor` prop created by `useEditor` and injects it into both `EditorContext` (for editor state) and `BusContext` (for events), making the editor instance available to all descendant components without manual prop passing.

### How do I access the TipTap editor instance in a child component?

Import and call `useEditorInstance()` from `@/store/editor` (or the library's public exports). This hook wraps `useContext(EditorContext)` and returns the `{ editor }` object, allowing you to invoke TipTap commands like `editor?.chain().focus().toggleBold().run()` directly from any component inside the `RichTextProvider` tree.

### Why does the repository use a separate event bus alongside React Context?

The `ReactBusProvider` (using `mitt` from [`src/components/ReactBus.tsx`](https://github.com/hunghg255/reactjs-tiptap-editor/blob/main/src/components/ReactBus.tsx)) handles cross-component communication for UI-level events—such as opening dialogs or coordinating toolbar actions—that do not strictly depend on the editor's document state. This separation keeps `EditorContext` focused on the TipTap editor instance while the bus handles application-level events, improving modularity and reducing unnecessary re-renders on the editor context.

### Can I use reactjs-tiptap-editor without React Context?

While the library is architected around React Context for state management, you could technically pass the editor instance directly through props. However, you would lose the benefits of the built-in `RichTextProvider`, `useEditorInstance` hook, and the event bus architecture, requiring you to reimplement much of the decoupling logic found in [`src/components/RichTextProvider.tsx`](https://github.com/hunghg255/reactjs-tiptap-editor/blob/main/src/components/RichTextProvider.tsx) and [`src/components/ReactBus.tsx`](https://github.com/hunghg255/reactjs-tiptap-editor/blob/main/src/components/ReactBus.tsx).