Skip to content

Commit 88a2841

Browse files
authored
docs: add lexical docs for configuring jsx converters for internal links and overriding them (#11415)
This PR improves existing JSX converter docs and adds 2 new sections: - **converting internal links** - addresses why a `"found internal link, but internalDocToHref is not provided"` error is thrown, and how to get around it - **Overriding default JSX Converters**
1 parent 7e713a4 commit 88a2841

File tree

1 file changed

+103
-19
lines changed

1 file changed

+103
-19
lines changed

docs/rich-text/converters.mdx

Lines changed: 103 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Lexical saves data in JSON - this is great for storage and flexibility and allow
1010

1111
## Lexical => JSX
1212

13-
If your frontend uses React, converting Lexical to JSX is the recommended way to render rich text content. Import the `RichText` component from `@payloadcms/richtext-lexical/react` and pass the Lexical content to it:
13+
For React-based frontends, converting Lexical content to JSX is the recommended rendering approach. Import the RichText component from @payloadcms/richtext-lexical/react and pass the Lexical content to it:
1414

1515
```tsx
1616
import React from 'react'
@@ -24,46 +24,130 @@ export const MyComponent = ({ data }: { data: SerializedEditorState }) => {
2424
}
2525
```
2626

27-
The `RichText` component includes built-in serializers for common Lexical nodes but allows customization through the `converters` prop.
28-
29-
In our website template [you have an example](https://github.com/payloadcms/payload/blob/main/templates/website/src/components/RichText/index.tsx) of how to use `converters` to render custom blocks.
30-
27+
The `RichText` component includes built-in serializers for common Lexical nodes but allows customization through the `converters` prop. In our [website template](https://github.com/payloadcms/payload/blob/main/templates/website/src/components/RichText/index.tsx) you have an example of how to use `converters` to render custom blocks, custom nodes and override existing converters.
3128

3229
<Banner type="default">
33-
The JSX converter expects the input data to be fully populated. When fetching data, ensure the `depth` setting is high enough, to ensure that lexical nodes such as uploads are populated.
30+
When fetching data, ensure your `depth` setting is high enough to fully populate Lexical nodes such as uploads. The JSX converter requires fully populated data to work correctly.
3431
</Banner>
3532

36-
### Converting Lexical Blocks to JSX
33+
### Converting Internal Links
34+
35+
By default, Payload doesn't know how to convert **internal** links to JSX, as it doesn't know what the corresponding URL of the internal link is. You'll notice that you get a "found internal link, but internalDocToHref is not provided" error in the console when you try to render content with internal links.
3736

38-
In order to convert lexical blocks or inline blocks to JSX, you will have to pass the converter for your block to the RichText component. This converter is not included by default, as Payload doesn't know how to render your custom blocks.
37+
To fix this, you need to pass the `internalDocToHref` prop to `LinkJSXConverter`. This prop is a function that receives the link node and returns the URL of the document.
3938

4039
```tsx
41-
import React from 'react'
40+
import type { DefaultNodeTypes, SerializedLinkNode } from '@payloadcms/richtext-lexical'
41+
import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'
42+
4243
import {
4344
type JSXConvertersFunction,
45+
LinkJSXConverter,
4446
RichText,
4547
} from '@payloadcms/richtext-lexical/react'
48+
import React from 'react'
49+
50+
const internalDocToHref = ({ linkNode }: { linkNode: SerializedLinkNode }) => {
51+
const { relationTo, value } = linkNode.fields.doc!
52+
if (typeof value !== 'object') {
53+
throw new Error('Expected value to be an object')
54+
}
55+
const slug = value.slug
56+
return relationTo === 'posts' ? `/posts/${slug}` : `/${slug}`
57+
}
58+
59+
const jsxConverters: JSXConvertersFunction<DefaultNodeTypes> = ({ defaultConverters }) => ({
60+
...defaultConverters,
61+
...LinkJSXConverter({ internalDocToHref }),
62+
})
63+
64+
export const MyComponent: React.FC<{
65+
lexicalData: SerializedEditorState
66+
}> = ({ lexicalData }) => {
67+
return <RichText converters={jsxConverters} data={lexicalData} />
68+
}
69+
```
70+
71+
### Converting Lexical Blocks
72+
73+
To convert Lexical Blocks or Inline Blocks to JSX, pass the converter for your block to the `RichText` component. This converter is not included by default, as Payload doesn't know how to render your custom blocks.
74+
75+
```tsx
76+
'use client'
77+
import type { MyInlineBlock, MyTextBlock } from '@/payload-types'
78+
import type { DefaultNodeTypes, SerializedBlockNode } from '@payloadcms/richtext-lexical'
4679
import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'
4780

48-
const jsxConverters: JSXConvertersFunction = ({ defaultConverters }) => ({
81+
import { type JSXConvertersFunction, RichText } from '@payloadcms/richtext-lexical/react'
82+
import React from 'react'
83+
84+
// Extend the default node types with your custom blocks for full type safety
85+
type NodeTypes = DefaultNodeTypes | SerializedBlockNode<MyInlineBlock | MyTextBlock>
86+
87+
const jsxConverters: JSXConvertersFunction<NodeTypes> = ({ defaultConverters }) => ({
4988
...defaultConverters,
5089
blocks: {
51-
// myTextBlock is the slug of the block
90+
// Each key should match your block's slug
5291
myTextBlock: ({ node }) => <div style={{ backgroundColor: 'red' }}>{node.fields.text}</div>,
5392
},
5493
inlineBlocks: {
55-
// myInlineBlock is the slug of the block
94+
// Each key should match your inline block's slug
5695
myInlineBlock: ({ node }) => <span>{node.fields.text}</span>,
5796
},
5897
})
5998

60-
export const MyComponent = ({ lexicalData }) => {
61-
return (
62-
<RichText
63-
converters={jsxConverters}
64-
data={lexicalData.lexicalWithBlocks as SerializedEditorState}
65-
/>
66-
)
99+
export const MyComponent: React.FC<{
100+
lexicalData: SerializedEditorState
101+
}> = ({ lexicalData }) => {
102+
return <RichText converters={jsxConverters} data={lexicalData} />
103+
}
104+
```
105+
106+
### Overriding Default JSX Converters
107+
108+
You can override any of the default JSX converters by passing passing your custom converter, keyed to the node type, to the `converters` prop / the converters function.
109+
110+
Example - overriding the upload node converter to use next/image:
111+
112+
```tsx
113+
'use client'
114+
import type { DefaultNodeTypes, SerializedUploadNode } from '@payloadcms/richtext-lexical'
115+
import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'
116+
117+
import { type JSXConvertersFunction, RichText } from '@payloadcms/richtext-lexical/react'
118+
import Image from 'next/image'
119+
import React from 'react'
120+
121+
type NodeTypes = DefaultNodeTypes
122+
123+
// Custom upload converter component that uses next/image
124+
const CustomUploadComponent: React.FC<{
125+
node: SerializedUploadNode
126+
}> = ({ node }) => {
127+
if (node.relationTo === 'uploads') {
128+
const uploadDoc = node.value
129+
if (typeof uploadDoc !== 'object') {
130+
return null
131+
}
132+
const { alt, height, url, width } = uploadDoc
133+
return <Image alt={alt} height={height} src={url} width={width} />
134+
}
135+
136+
return null
137+
}
138+
139+
const jsxConverters: JSXConvertersFunction<NodeTypes> = ({ defaultConverters }) => ({
140+
...defaultConverters,
141+
// Override the default upload converter
142+
upload: ({ node }) => {
143+
return <CustomUploadComponent node={node} />
144+
},
145+
})
146+
147+
export const MyComponent: React.FC<{
148+
lexicalData: SerializedEditorState
149+
}> = ({ lexicalData }) => {
150+
return <RichText converters={jsxConverters} data={lexicalData} />
67151
}
68152
```
69153

0 commit comments

Comments
 (0)