> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/ueberdosis/tiptap/llms.txt
> Use this file to discover all available pages before exploring further.

# React

> Learn how to integrate Tiptap with React using hooks and components

Tiptap provides first-class support for React with hooks, components, and utilities that integrate seamlessly with React's ecosystem.

## Installation

<Steps>
  <Step title="Install the packages">
    Install the React package along with the core package and any extensions you need:

    ```bash theme={null}
    npm install @tiptap/react @tiptap/core @tiptap/starter-kit
    ```
  </Step>

  <Step title="Import and use">
    Import the necessary components and hooks in your React component:

    ```tsx theme={null}
    import { useEditor, EditorContent } from '@tiptap/react'
    import StarterKit from '@tiptap/starter-kit'
    ```
  </Step>
</Steps>

## Core Concepts

### useEditor Hook

The `useEditor` hook is the primary way to create and manage an editor instance in React. It handles the editor lifecycle automatically.

#### Type Signature

```typescript theme={null}
function useEditor(
  options: UseEditorOptions,
  deps?: DependencyList
): Editor | null

interface UseEditorOptions extends Partial<EditorOptions> {
  /**
   * Whether to render the editor on the first render.
   * If client-side rendering, set this to `true`.
   * If server-side rendering, set this to `false`.
   * @default true
   */
  immediatelyRender?: boolean
  
  /**
   * Whether to re-render the editor on each transaction.
   * This is legacy behavior that will be removed in future versions.
   * @default false
   */
  shouldRerenderOnTransaction?: boolean
}
```

#### Basic Usage

```tsx theme={null}
import { useEditor, EditorContent } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'

function MyEditor() {
  const editor = useEditor({
    extensions: [StarterKit],
    content: '<p>Hello World!</p>',
  })

  return <EditorContent editor={editor} />
}
```

#### SSR Configuration

For server-side rendering (Next.js, Remix, etc.), you must set `immediatelyRender` to `false`:

```tsx theme={null}
function MyEditor() {
  const editor = useEditor({
    immediatelyRender: false, // Required for SSR
    extensions: [StarterKit],
    content: '<p>Hello World!</p>',
  })

  return <EditorContent editor={editor} />
}
```

#### Performance Optimization

For better performance, disable transaction re-renders and use `useEditorState` instead:

```tsx theme={null}
import { useEditor, useEditorState, EditorContent } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'

function MyEditor() {
  const editor = useEditor({
    immediatelyRender: true,
    shouldRerenderOnTransaction: false, // Disable automatic re-renders
    extensions: [StarterKit],
    content: '<p>Hello World!</p>',
  })

  // Select only the state you need
  const { isBold, isItalic } = useEditorState({
    editor,
    selector: (ctx) => ({
      isBold: ctx.editor.isActive('bold'),
      isItalic: ctx.editor.isActive('italic'),
    }),
  })

  return (
    <div>
      <button onClick={() => editor?.chain().focus().toggleBold().run()}>
        Bold {isBold ? '(active)' : ''}
      </button>
      <button onClick={() => editor?.chain().focus().toggleItalic().run()}>
        Italic {isItalic ? '(active)' : ''}
      </button>
      <EditorContent editor={editor} />
    </div>
  )
}
```

### EditorContent Component

The `EditorContent` component renders the actual editor interface.

```tsx theme={null}
import type { Editor } from '@tiptap/core'
import { EditorContent } from '@tiptap/react'

interface EditorContentProps {
  editor: Editor | null
  className?: string
  style?: React.CSSProperties
  // ... other HTML div props
}
```

#### Usage Example

```tsx theme={null}
function MyEditor() {
  const editor = useEditor({
    extensions: [StarterKit],
    content: '<p>Hello World!</p>',
  })

  return (
    <EditorContent 
      editor={editor} 
      className="prose max-w-none"
      style={{ minHeight: '200px' }}
    />
  )
}
```

## New Tiptap Component API

Tiptap 3.18+ introduces a new component-based API that provides a cleaner, more React-idiomatic way to build editors.

### Tiptap Provider Component

The `Tiptap` component provides the editor instance via context, making it available to all child components.

```tsx theme={null}
import { Tiptap, useEditor, useTiptap } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'

function Toolbar() {
  // Access editor from context
  const { editor } = useTiptap()

  return (
    <div>
      <button onClick={() => editor.chain().focus().toggleBold().run()}>
        Bold
      </button>
      <button onClick={() => editor.chain().focus().toggleItalic().run()}>
        Italic
      </button>
    </div>
  )
}

function App() {
  const editor = useEditor({ 
    extensions: [StarterKit],
    content: '<p>Hello World!</p>'
  })

  return (
    <Tiptap editor={editor}>
      <Toolbar />
      <Tiptap.Content className="editor" />
    </Tiptap>
  )
}
```

### useTiptap Hook

Access the editor instance from any component inside the `Tiptap` provider:

```tsx theme={null}
import { useTiptap } from '@tiptap/react'

function WordCount() {
  const { editor } = useTiptap()
  const count = editor.state.doc.textContent.split(/\s+/).filter(Boolean).length

  return <span>{count} words</span>
}
```

### useTiptapState Hook

Select specific editor state slices for optimized re-renders:

```tsx theme={null}
import { useTiptapState } from '@tiptap/react'

function FormattingButtons() {
  const state = useTiptapState(
    (ctx) => ({
      isBold: ctx.editor.isActive('bold'),
      isItalic: ctx.editor.isActive('italic'),
      isStrike: ctx.editor.isActive('strike'),
    }),
    // Optional equality function
    (a, b) => a.isBold === b.isBold && a.isItalic === b.isItalic && a.isStrike === b.isStrike
  )

  return (
    <div>
      <button disabled={!state.isBold}>Bold</button>
      <button disabled={!state.isItalic}>Italic</button>
      <button disabled={!state.isStrike}>Strike</button>
    </div>
  )
}
```

## Complete Working Example

<CodeGroup>
  ```tsx Basic Editor theme={null}
  import { useEditor, EditorContent } from '@tiptap/react'
  import StarterKit from '@tiptap/starter-kit'
  import './styles.css'

  export default function Editor() {
    const editor = useEditor({
      extensions: [StarterKit],
      content: `
        <h2>Hi there,</h2>
        <p>this is a <em>basic</em> example of <strong>Tiptap</strong>.</p>
      `,
    })

    return <EditorContent editor={editor} />
  }
  ```

  ```tsx With Toolbar theme={null}
  import { useEditor, EditorContent } from '@tiptap/react'
  import StarterKit from '@tiptap/starter-kit'
  import { TextStyleKit } from '@tiptap/extension-text-style'

  function MenuBar({ editor }) {
    if (!editor) return null

    return (
      <div className="menu-bar">
        <button
          onClick={() => editor.chain().focus().toggleBold().run()}
          disabled={!editor.can().chain().focus().toggleBold().run()}
          className={editor.isActive('bold') ? 'is-active' : ''}
        >
          Bold
        </button>
        <button
          onClick={() => editor.chain().focus().toggleItalic().run()}
          disabled={!editor.can().chain().focus().toggleItalic().run()}
          className={editor.isActive('italic') ? 'is-active' : ''}
        >
          Italic
        </button>
        <button
          onClick={() => editor.chain().focus().toggleHeading({ level: 1 }).run()}
          className={editor.isActive('heading', { level: 1 }) ? 'is-active' : ''}
        >
          H1
        </button>
        <button
          onClick={() => editor.chain().focus().toggleBulletList().run()}
          className={editor.isActive('bulletList') ? 'is-active' : ''}
        >
          Bullet List
        </button>
      </div>
    )
  }

  export default function Editor() {
    const editor = useEditor({
      extensions: [TextStyleKit, StarterKit],
      content: '<p>Hello World!</p>',
    })

    return (
      <>
        <MenuBar editor={editor} />
        <EditorContent editor={editor} />
      </>
    )
  }
  ```

  ```tsx New Tiptap Component API theme={null}
  import { Tiptap, useEditor, useTiptap, useTiptapState } from '@tiptap/react'
  import { BubbleMenu } from '@tiptap/react/menus'
  import StarterKit from '@tiptap/starter-kit'

  function Toolbar() {
    const { editor } = useTiptap()
    const { isBold } = useTiptapState(ctx => ({ 
      isBold: ctx.editor.isActive('bold') 
    }))

    return (
      <button onClick={() => editor.chain().focus().toggleBold().run()}>
        {isBold ? 'Bold (on)' : 'Bold (off)'}
      </button>
    )
  }

  function App() {
    const editor = useEditor({ 
      extensions: [StarterKit],
      content: '<p>Hello from Tiptap</p>'
    })

    return (
      <Tiptap editor={editor}>
        <Toolbar />
        <Tiptap.Content />
        <BubbleMenu>
          <button onClick={() => editor?.chain().focus().toggleBold().run()}>
            Bold
          </button>
        </BubbleMenu>
      </Tiptap>
    )
  }
  ```
</CodeGroup>

## Event Handlers

Handle editor events through the `useEditor` options:

```tsx theme={null}
const editor = useEditor({
  extensions: [StarterKit],
  content: '<p>Hello World!</p>',
  onUpdate: ({ editor }) => {
    console.log('Content updated:', editor.getHTML())
  },
  onCreate: ({ editor }) => {
    console.log('Editor created')
  },
  onFocus: ({ editor, event }) => {
    console.log('Editor focused')
  },
  onBlur: ({ editor, event }) => {
    console.log('Editor blurred')
  },
  onSelectionUpdate: ({ editor }) => {
    console.log('Selection updated')
  },
})
```

## Dependencies Array

The second parameter of `useEditor` accepts a dependencies array, similar to `useEffect`. When dependencies change, the editor is recreated:

```tsx theme={null}
function MyEditor({ userId }) {
  const editor = useEditor({
    extensions: [StarterKit],
    content: '<p>Hello World!</p>',
  }, [userId]) // Recreate editor when userId changes

  return <EditorContent editor={editor} />
}
```

## TypeScript Support

Tiptap has full TypeScript support with comprehensive type definitions:

```tsx theme={null}
import type { Editor } from '@tiptap/core'
import type { UseEditorOptions } from '@tiptap/react'
import { useEditor, EditorContent } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'

const editorConfig: UseEditorOptions = {
  extensions: [StarterKit],
  content: '<p>Hello World!</p>',
}

function MyEditor() {
  const editor: Editor | null = useEditor(editorConfig)

  return <EditorContent editor={editor} />
}
```

## Advanced: Custom Node Views

Create React components as custom node views:

```tsx theme={null}
import { NodeViewWrapper, NodeViewContent, ReactNodeViewRenderer } from '@tiptap/react'
import { Node } from '@tiptap/core'

// React component for the node
function Component(props) {
  return (
    <NodeViewWrapper className="custom-node">
      <div>Custom node content:</div>
      <NodeViewContent />
    </NodeViewWrapper>
  )
}

// Define the extension
const CustomNode = Node.create({
  name: 'customNode',
  
  group: 'block',
  
  content: 'inline*',
  
  parseHTML() {
    return [{ tag: 'div[data-type="custom-node"]' }]
  },
  
  renderHTML({ HTMLAttributes }) {
    return ['div', { 'data-type': 'custom-node', ...HTMLAttributes }, 0]
  },
  
  addNodeView() {
    return ReactNodeViewRenderer(Component)
  },
})
```

## Menus

Bubble and floating menus are now separate imports to keep `@floating-ui/dom` optional:

```tsx theme={null}
import { BubbleMenu, FloatingMenu } from '@tiptap/react/menus'
import { useEditor, EditorContent } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'

function Editor() {
  const editor = useEditor({
    extensions: [StarterKit],
    content: '<p>Hello World!</p>',
  })

  return (
    <>
      <EditorContent editor={editor} />
      
      <BubbleMenu editor={editor}>
        <button onClick={() => editor?.chain().focus().toggleBold().run()}>
          Bold
        </button>
      </BubbleMenu>
      
      <FloatingMenu editor={editor}>
        <button onClick={() => editor?.chain().focus().toggleHeading({ level: 1 }).run()}>
          H1
        </button>
      </FloatingMenu>
    </>
  )
}
```

## Next Steps

<CardGroup cols={2}>
  <Card title="Extensions" icon="puzzle-piece" href="/extensions">
    Explore available extensions to enhance your editor
  </Card>

  <Card title="Commands" icon="terminal" href="/api/commands">
    Learn about editor commands and chains
  </Card>

  <Card title="Styling" icon="paintbrush" href="/guide/styling">
    Customize the editor's appearance
  </Card>

  <Card title="Node Views" icon="cube" href="/guide/node-views">
    Create interactive custom nodes
  </Card>
</CardGroup>
