0
# Context and Hooks
1
2
React Context and hooks for accessing editor state, configuration, and building custom controls. The context system enables deep integration with the editor while maintaining consistent behavior across all components.
3
4
## Capabilities
5
6
### useRichTextEditorContext Hook
7
8
Primary hook for accessing the rich text editor's context, including editor instance, styling functions, and configuration options.
9
10
```typescript { .api }
11
/**
12
* Hook for accessing rich text editor context
13
* Must be used within a RichTextEditor component tree
14
* @returns RichTextEditorContext object with editor state and configuration
15
* @throws Error if used outside of RichTextEditor provider
16
*/
17
function useRichTextEditorContext(): RichTextEditorContext;
18
19
interface RichTextEditorContext {
20
/** Current Tiptap editor instance */
21
editor: Editor | null;
22
/** Mantine styling API for consistent component styling */
23
getStyles: GetStylesApi<RichTextEditorFactory>;
24
/** Merged accessibility labels for all controls */
25
labels: RichTextEditorLabels;
26
/** Whether code highlighting styles are enabled */
27
withCodeHighlightStyles: boolean | undefined;
28
/** Whether typography styles are enabled */
29
withTypographyStyles: boolean | undefined;
30
/** Current component variant */
31
variant: string | undefined;
32
/** Whether default styles are disabled */
33
unstyled: boolean | undefined;
34
/** Callback for source code view toggle events */
35
onSourceCodeTextSwitch?: (isSourceCodeModeActive: boolean) => void;
36
}
37
```
38
39
**Usage Example:**
40
41
```typescript
42
import { useRichTextEditorContext } from "@mantine/tiptap";
43
import { IconCustomFormat } from "@tabler/icons-react";
44
45
function CustomControl() {
46
const { editor, labels, getStyles } = useRichTextEditorContext();
47
48
const isActive = editor?.isActive('bold') || false;
49
const canExecute = editor?.can().toggleBold() || false;
50
51
return (
52
<button
53
{...getStyles('control')}
54
disabled={!canExecute}
55
onClick={() => editor?.chain().focus().toggleBold().run()}
56
aria-label={labels.boldControlLabel}
57
data-active={isActive}
58
>
59
<IconCustomFormat size={16} />
60
</button>
61
);
62
}
63
```
64
65
### Context Properties
66
67
#### Editor Instance
68
69
Access the current Tiptap editor instance for direct manipulation:
70
71
```typescript
72
function EditorStateDisplay() {
73
const { editor } = useRichTextEditorContext();
74
75
if (!editor) {
76
return <div>Editor not ready</div>;
77
}
78
79
const wordCount = editor.storage.characterCount?.words() || 0;
80
const canUndo = editor.can().undo();
81
const canRedo = editor.can().redo();
82
83
return (
84
<div>
85
<p>Words: {wordCount}</p>
86
<p>Can Undo: {canUndo ? 'Yes' : 'No'}</p>
87
<p>Can Redo: {canRedo ? 'Yes' : 'No'}</p>
88
</div>
89
);
90
}
91
```
92
93
#### Styling Integration
94
95
Use the styling API to maintain consistency with built-in components:
96
97
```typescript
98
function StyledCustomControl() {
99
const { getStyles } = useRichTextEditorContext();
100
101
return (
102
<div {...getStyles('controlsGroup')}>
103
<button {...getStyles('control')}>
104
Custom Control
105
</button>
106
</div>
107
);
108
}
109
```
110
111
#### Labels and Internationalization
112
113
Access merged labels for consistent accessibility:
114
115
```typescript
116
function AccessibleControl() {
117
const { labels, editor } = useRichTextEditorContext();
118
119
return (
120
<button
121
aria-label={labels.boldControlLabel}
122
title={labels.boldControlLabel}
123
onClick={() => editor?.chain().focus().toggleBold().run()}
124
>
125
B
126
</button>
127
);
128
}
129
```
130
131
## Building Custom Controls
132
133
### Basic Custom Control
134
135
```typescript
136
import { useRichTextEditorContext } from "@mantine/tiptap";
137
138
interface CustomControlProps {
139
command: string;
140
icon: React.ReactNode;
141
label: string;
142
}
143
144
function CustomControl({ command, icon, label }: CustomControlProps) {
145
const { editor, getStyles } = useRichTextEditorContext();
146
147
const isActive = editor?.isActive(command) || false;
148
const canExecute = editor?.can()[`toggle${command}`]?.() || false;
149
150
const handleClick = () => {
151
editor?.chain().focus()[`toggle${command}`]?.().run();
152
};
153
154
return (
155
<button
156
{...getStyles('control')}
157
onClick={handleClick}
158
disabled={!canExecute}
159
aria-label={label}
160
data-active={isActive}
161
>
162
{icon}
163
</button>
164
);
165
}
166
167
// Usage
168
<CustomControl
169
command="bold"
170
icon={<IconBold />}
171
label="Toggle Bold"
172
/>
173
```
174
175
### Advanced Custom Control with State
176
177
```typescript
178
import { useState } from "react";
179
import { useRichTextEditorContext } from "@mantine/tiptap";
180
181
function FontSizeControl() {
182
const { editor, getStyles } = useRichTextEditorContext();
183
const [isOpen, setIsOpen] = useState(false);
184
185
const currentSize = editor?.getAttributes('textStyle').fontSize || '16px';
186
187
const sizes = ['12px', '14px', '16px', '18px', '20px', '24px'];
188
189
const applySize = (size: string) => {
190
editor?.chain().focus().setFontSize(size).run();
191
setIsOpen(false);
192
};
193
194
return (
195
<div style={{ position: 'relative' }}>
196
<button
197
{...getStyles('control')}
198
onClick={() => setIsOpen(!isOpen)}
199
aria-label="Font Size"
200
>
201
{currentSize}
202
</button>
203
204
{isOpen && (
205
<div style={{ position: 'absolute', top: '100%', background: 'white', border: '1px solid #ccc' }}>
206
{sizes.map(size => (
207
<button
208
key={size}
209
onClick={() => applySize(size)}
210
style={{ display: 'block', width: '100%', fontSize: size }}
211
>
212
{size}
213
</button>
214
))}
215
</div>
216
)}
217
</div>
218
);
219
}
220
```
221
222
### Building Complex Custom Controls
223
224
```typescript
225
import { useRichTextEditorContext } from "@mantine/tiptap";
226
import { IconCustomIcon } from "@tabler/icons-react";
227
228
// Build a complex custom control with multiple features
229
function CustomFormatControl() {
230
const { editor, labels, getStyles } = useRichTextEditorContext();
231
232
const isActive = editor?.isActive('customFormat') || false;
233
const canExecute = editor?.can().toggleCustomFormat?.() || false;
234
235
const handleClick = () => {
236
editor?.chain().focus().toggleCustomFormat().run();
237
};
238
239
return (
240
<button
241
{...getStyles('control')}
242
onClick={handleClick}
243
disabled={!canExecute}
244
aria-label="Apply Custom Format"
245
data-active={isActive}
246
>
247
<IconCustomIcon size={16} />
248
</button>
249
);
250
}
251
252
// Usage
253
<RichTextEditor editor={editor}>
254
<RichTextEditor.Toolbar>
255
<RichTextEditor.ControlsGroup>
256
<CustomFormatControl />
257
</RichTextEditor.ControlsGroup>
258
</RichTextEditor.Toolbar>
259
<RichTextEditor.Content />
260
</RichTextEditor>
261
```
262
263
## Context Provider
264
265
The context is automatically provided by the `RichTextEditor` component:
266
267
```typescript
268
// Context is provided automatically
269
<RichTextEditor editor={editor}>
270
{/* All child components have access to context */}
271
<RichTextEditor.Toolbar>
272
<CustomControl /> {/* Can use useRichTextEditorContext */}
273
<RichTextEditor.Bold /> {/* Uses context internally */}
274
</RichTextEditor.Toolbar>
275
<RichTextEditor.Content />
276
</RichTextEditor>
277
```
278
279
## Error Handling
280
281
The context hook includes built-in error handling:
282
283
```typescript
284
function SafeCustomControl() {
285
try {
286
const context = useRichTextEditorContext();
287
// Use context safely
288
return <button>Custom Control</button>;
289
} catch (error) {
290
// Handle context not found error
291
console.error('RichTextEditor context not found:', error);
292
return null;
293
}
294
}
295
```
296
297
## TypeScript Integration
298
299
Full TypeScript support with proper type inference:
300
301
```typescript
302
import type { Editor } from "@tiptap/react";
303
import type { RichTextEditorContext } from "@mantine/tiptap";
304
305
function TypedCustomControl() {
306
const context: RichTextEditorContext = useRichTextEditorContext();
307
const editor: Editor | null = context.editor;
308
309
// TypeScript knows the exact shape of context
310
const labels = context.labels; // RichTextEditorLabels
311
const getStyles = context.getStyles; // GetStylesApi<RichTextEditorFactory>
312
313
return (
314
<button onClick={() => editor?.chain().focus().toggleBold().run()}>
315
Bold
316
</button>
317
);
318
}
319
```
320
321
## Best Practices
322
323
### Performance Optimization
324
325
```typescript
326
import { useCallback } from "react";
327
328
function OptimizedControl() {
329
const { editor } = useRichTextEditorContext();
330
331
// Memoize event handlers
332
const handleClick = useCallback(() => {
333
editor?.chain().focus().toggleBold().run();
334
}, [editor]);
335
336
return <button onClick={handleClick}>Bold</button>;
337
}
338
```
339
340
### Conditional Rendering
341
342
```typescript
343
function ConditionalControl({ showAdvanced }: { showAdvanced: boolean }) {
344
const { editor } = useRichTextEditorContext();
345
346
if (!editor || !showAdvanced) {
347
return null;
348
}
349
350
return (
351
<button onClick={() => editor.chain().focus().toggleCode().run()}>
352
Code
353
</button>
354
);
355
}
356
```
357
358
### State Synchronization
359
360
```typescript
361
import { useEffect, useState } from "react";
362
363
function EditorStateSync() {
364
const { editor } = useRichTextEditorContext();
365
const [isBold, setIsBold] = useState(false);
366
367
useEffect(() => {
368
if (!editor) return;
369
370
const updateState = () => {
371
setIsBold(editor.isActive('bold'));
372
};
373
374
// Listen to editor updates
375
editor.on('selectionUpdate', updateState);
376
editor.on('transaction', updateState);
377
378
return () => {
379
editor.off('selectionUpdate', updateState);
380
editor.off('transaction', updateState);
381
};
382
}, [editor]);
383
384
return <div>Text is {isBold ? 'bold' : 'normal'}</div>;
385
}
386
```