0
# Content Rendering
1
2
JSON-to-React rendering system for displaying editor content as React components with customizable handlers.
3
4
## Capabilities
5
6
### RemirrorRenderer Component
7
8
Main component for rendering Remirror JSON content as React components.
9
10
```typescript { .api }
11
/**
12
* Main recursive JSON-to-React renderer for displaying editor content
13
*/
14
interface RemirrorRenderer extends React.Component<RemirrorRendererProps> {}
15
16
interface RemirrorRendererProps {
17
/** Remirror JSON document to render */
18
json: RemirrorJSON;
19
/** Custom type mapping for nodes and marks */
20
typeMap?: Record<string, ComponentType>;
21
/** Custom mark mapping */
22
markMap?: MarkMap;
23
/** Additional props to pass to rendered components */
24
componentProps?: Record<string, any>;
25
/** Error boundary component */
26
errorBoundary?: ComponentType<{ error: Error; children: React.ReactNode }>;
27
}
28
29
interface RemirrorJSON {
30
/** Document type */
31
type: 'doc';
32
/** Document content nodes */
33
content?: RemirrorJSONNode[];
34
/** Document attributes */
35
attrs?: Record<string, any>;
36
}
37
38
interface RemirrorJSONNode {
39
/** Node type */
40
type: string;
41
/** Node content */
42
content?: RemirrorJSONNode[];
43
/** Node attributes */
44
attrs?: Record<string, any>;
45
/** Node marks */
46
marks?: RemirrorJSONMark[];
47
/** Text content (for text nodes) */
48
text?: string;
49
}
50
51
interface RemirrorJSONMark {
52
/** Mark type */
53
type: string;
54
/** Mark attributes */
55
attrs?: Record<string, any>;
56
}
57
```
58
59
**Usage Example:**
60
61
```typescript
62
import React from 'react';
63
import { RemirrorRenderer } from '@remirror/react';
64
65
// Sample Remirror JSON content
66
const sampleContent = {
67
type: 'doc',
68
content: [
69
{
70
type: 'paragraph',
71
content: [
72
{
73
type: 'text',
74
text: 'Hello ',
75
},
76
{
77
type: 'text',
78
text: 'world',
79
marks: [{ type: 'bold' }],
80
},
81
{
82
type: 'text',
83
text: '!',
84
},
85
],
86
},
87
],
88
};
89
90
function ContentViewer() {
91
return (
92
<div>
93
<h2>Rendered Content:</h2>
94
<RemirrorRenderer json={sampleContent} />
95
</div>
96
);
97
}
98
```
99
100
### Doc Component
101
102
Document root renderer component for complete documents.
103
104
```typescript { .api }
105
/**
106
* Document root renderer component for complete document rendering
107
*/
108
interface Doc extends React.Component<DocProps> {}
109
110
interface DocProps {
111
/** Remirror JSON document */
112
json: RemirrorJSON;
113
/** Document-level attributes */
114
attributes?: Record<string, any>;
115
/** Custom document wrapper component */
116
wrapper?: ComponentType<{ children: React.ReactNode }>;
117
}
118
```
119
120
### Content Handlers
121
122
Specialized components for rendering different types of content.
123
124
```typescript { .api }
125
/**
126
* Handler for rendering callout blocks
127
*/
128
interface Callout extends React.Component<CalloutProps> {}
129
130
interface CalloutProps {
131
/** Callout type/variant */
132
type?: 'info' | 'warning' | 'error' | 'success';
133
/** Callout title */
134
title?: string;
135
/** Callout content */
136
children: React.ReactNode;
137
/** Custom icon */
138
icon?: React.ReactNode;
139
}
140
141
/**
142
* Handler for rendering code blocks with syntax highlighting
143
*/
144
interface CodeBlock extends React.Component<CodeBlockProps> {}
145
146
interface CodeBlockProps {
147
/** Programming language for syntax highlighting */
148
language?: string;
149
/** Code content */
150
code: string;
151
/** Whether to show line numbers */
152
showLineNumbers?: boolean;
153
/** Custom theme for syntax highlighting */
154
theme?: string;
155
}
156
157
/**
158
* Handler for rendering heading elements
159
*/
160
interface Heading extends React.Component<HeadingProps> {}
161
162
interface HeadingProps {
163
/** Heading level (1-6) */
164
level: 1 | 2 | 3 | 4 | 5 | 6;
165
/** Heading content */
166
children: React.ReactNode;
167
/** Custom ID for anchor links */
168
id?: string;
169
/** Additional CSS classes */
170
className?: string;
171
}
172
173
/**
174
* Handler for rendering text content with marks
175
*/
176
interface TextHandler extends React.Component<TextHandlerProps> {}
177
178
interface TextHandlerProps {
179
/** Text content */
180
text: string;
181
/** Applied marks */
182
marks?: RemirrorJSONMark[];
183
/** Custom mark renderers */
184
markRenderers?: Record<string, ComponentType>;
185
}
186
```
187
188
**Usage Example:**
189
190
```typescript
191
import React from 'react';
192
import {
193
RemirrorRenderer,
194
Callout,
195
CodeBlock,
196
Heading
197
} from '@remirror/react';
198
199
function CustomContentRenderer() {
200
const customTypeMap = {
201
callout: ({ type, title, children }) => (
202
<Callout type={type} title={title}>
203
{children}
204
</Callout>
205
),
206
codeBlock: ({ language, code }) => (
207
<CodeBlock
208
language={language}
209
code={code}
210
showLineNumbers={true}
211
theme="dark"
212
/>
213
),
214
heading: ({ level, children, id }) => (
215
<Heading level={level} id={id}>
216
{children}
217
</Heading>
218
),
219
};
220
221
const content = {
222
type: 'doc',
223
content: [
224
{
225
type: 'heading',
226
attrs: { level: 1 },
227
content: [{ type: 'text', text: 'Welcome' }],
228
},
229
{
230
type: 'callout',
231
attrs: { type: 'info', title: 'Note' },
232
content: [
233
{
234
type: 'paragraph',
235
content: [{ type: 'text', text: 'This is important!' }],
236
},
237
],
238
},
239
],
240
};
241
242
return (
243
<RemirrorRenderer
244
json={content}
245
typeMap={customTypeMap}
246
/>
247
);
248
}
249
```
250
251
### Handler Factories
252
253
Factory functions for creating specialized content handlers.
254
255
```typescript { .api }
256
/**
257
* Factory for creating iframe handlers
258
* @param options - Iframe configuration options
259
* @returns Iframe handler component
260
*/
261
function createIFrameHandler(
262
options: IFrameHandlerOptions
263
): ComponentType<IFrameProps>;
264
265
interface IFrameHandlerOptions {
266
/** Allowed domains for iframes */
267
allowedDomains?: string[];
268
/** Default iframe attributes */
269
defaultAttributes?: Record<string, string>;
270
/** Security settings */
271
sandbox?: string[];
272
/** Error fallback component */
273
errorFallback?: ComponentType;
274
}
275
276
interface IFrameProps {
277
/** Iframe source URL */
278
src: string;
279
/** Iframe title */
280
title?: string;
281
/** Iframe dimensions */
282
width?: number | string;
283
height?: number | string;
284
/** Additional attributes */
285
attributes?: Record<string, string>;
286
}
287
288
/**
289
* Factory for creating link handlers
290
* @param options - Link configuration options
291
* @returns Link handler component
292
*/
293
function createLinkHandler(
294
options: LinkHandlerOptions
295
): ComponentType<LinkProps>;
296
297
interface LinkHandlerOptions {
298
/** Whether to open external links in new tab */
299
openExternalInNewTab?: boolean;
300
/** Custom link click handler */
301
onClick?: (href: string, event: React.MouseEvent) => void;
302
/** Link validation function */
303
validateLink?: (href: string) => boolean;
304
/** Custom link styling */
305
className?: string;
306
}
307
308
interface LinkProps {
309
/** Link destination */
310
href: string;
311
/** Link text content */
312
children: React.ReactNode;
313
/** Link title attribute */
314
title?: string;
315
/** Whether link is external */
316
external?: boolean;
317
}
318
```
319
320
**Usage Example:**
321
322
```typescript
323
import React from 'react';
324
import {
325
RemirrorRenderer,
326
createIFrameHandler,
327
createLinkHandler
328
} from '@remirror/react';
329
330
function SafeContentRenderer() {
331
const safeIFrameHandler = createIFrameHandler({
332
allowedDomains: ['youtube.com', 'vimeo.com'],
333
sandbox: ['allow-scripts', 'allow-same-origin'],
334
errorFallback: () => <div>Iframe not allowed</div>,
335
});
336
337
const customLinkHandler = createLinkHandler({
338
openExternalInNewTab: true,
339
onClick: (href, event) => {
340
console.log('Link clicked:', href);
341
},
342
validateLink: (href) => !href.includes('malicious'),
343
});
344
345
const typeMap = {
346
iframe: safeIFrameHandler,
347
link: customLinkHandler,
348
};
349
350
const content = {
351
type: 'doc',
352
content: [
353
{
354
type: 'paragraph',
355
content: [
356
{
357
type: 'text',
358
text: 'Check out this video: ',
359
},
360
{
361
type: 'link',
362
attrs: { href: 'https://youtube.com/watch?v=example' },
363
content: [{ type: 'text', text: 'YouTube Video' }],
364
},
365
],
366
},
367
],
368
};
369
370
return (
371
<RemirrorRenderer
372
json={content}
373
typeMap={typeMap}
374
/>
375
);
376
}
377
```
378
379
## Mark Mapping
380
381
```typescript { .api }
382
/**
383
* Mapping of marks to React components for rendering
384
*/
385
type MarkMap = Record<string, ComponentType<MarkProps>>;
386
387
interface MarkProps {
388
/** Mark attributes */
389
attrs?: Record<string, any>;
390
/** Content to wrap with the mark */
391
children: React.ReactNode;
392
/** Mark type name */
393
markType: string;
394
}
395
396
/**
397
* Create a custom mark renderer
398
* @param markType - The mark type to handle
399
* @param component - React component to render the mark
400
* @returns Mark renderer function
401
*/
402
function createMarkRenderer(
403
markType: string,
404
component: ComponentType<MarkProps>
405
): (props: MarkProps) => React.ReactNode;
406
```
407
408
## Rendering Context
409
410
```typescript { .api }
411
/**
412
* Context for renderer components to access parent renderer state
413
*/
414
interface RendererContext {
415
/** Current document being rendered */
416
document: RemirrorJSON;
417
/** Current rendering path */
418
path: number[];
419
/** Type mappings */
420
typeMap: Record<string, ComponentType>;
421
/** Mark mappings */
422
markMap: MarkMap;
423
/** Renderer options */
424
options: RendererOptions;
425
}
426
427
interface RendererOptions {
428
/** Whether to sanitize HTML attributes */
429
sanitizeAttributes?: boolean;
430
/** Maximum nesting depth */
431
maxDepth?: number;
432
/** Error handling mode */
433
errorMode?: 'throw' | 'render' | 'ignore';
434
}
435
436
/**
437
* Hook to access renderer context
438
* @returns Current renderer context
439
*/
440
function useRendererContext(): RendererContext;
441
```
442
443
## Performance Optimization
444
445
```typescript { .api }
446
/**
447
* Optimized renderer for large documents
448
*/
449
interface OptimizedRenderer extends React.Component<OptimizedRendererProps> {}
450
451
interface OptimizedRendererProps extends RemirrorRendererProps {
452
/** Whether to use virtual scrolling */
453
virtualScrolling?: boolean;
454
/** Chunk size for rendering */
455
chunkSize?: number;
456
/** Whether to lazy load content */
457
lazyLoad?: boolean;
458
/** Intersection observer options for lazy loading */
459
observerOptions?: IntersectionObserverInit;
460
}
461
462
/**
463
* Memoized content renderer for better performance
464
* @param props - Renderer props
465
* @returns Memoized renderer component
466
*/
467
const MemoizedRenderer: React.MemoExoticComponent<
468
React.ComponentType<RemirrorRendererProps>
469
>;
470
```
471
472
**Usage Example:**
473
474
```typescript
475
import React from 'react';
476
import {
477
OptimizedRenderer,
478
MemoizedRenderer,
479
useRendererContext
480
} from '@remirror/react';
481
482
function LargeDocumentRenderer({ json }) {
483
return (
484
<OptimizedRenderer
485
json={json}
486
virtualScrolling={true}
487
chunkSize={50}
488
lazyLoad={true}
489
observerOptions={{
490
threshold: 0.1,
491
rootMargin: '50px',
492
}}
493
/>
494
);
495
}
496
497
function CustomNodeRenderer({ children }) {
498
const context = useRendererContext();
499
const isNested = context.path.length > 3;
500
501
return (
502
<div className={isNested ? 'nested-content' : 'top-level-content'}>
503
{children}
504
</div>
505
);
506
}
507
```