How to create table of contents from Contentful's Rich Text field
In this tutorial, I'm assuming you're using Gatsby to render your rich text fields. But the overall idea should be the same whether you're using a different framework!
1. Filter the rich text field
The first step is to filter the rich text field to only include the headings that we want. Usually, we only need all the h2's and the h3's to make a table of contents.
// table-of-contents.js
import React from 'react'
import { BLOCKS, MARKS } from '@contentful/rich-text-types'
const TableOfContents = ({ body }) => {
// the body is the json object returned by the rich text field
const headingTypes = [BLOCKS.HEADING_2, BLOCKS.HEADING_3]
const headings = body.json.content.filter(item => headingTypes.includes(item.nodeType))
// we're recreating the document shape of a rich text
// and we're only including the headings.
const document = {
nodeType: 'document',
content: headings,
}
}
In the code above, we are basically recreating the rich text's shape and only including the headings.
2. Style the headings
To show hierarchy, we can add a margin on all the h3's.
// table-of-contents.js
import React from 'react'
import { BLOCKS, MARKS } from '@contentful/rich-text-types'
const TableOfContents = ({ body }) => {
// the body is the json object returned by the rich text field
const headingTypes = [BLOCKS.HEADING_2, BLOCKS.HEADING_3]
const headings = body.json.content.filter(item => headingTypes.includes(item.nodeType))
const document = {
nodeType: 'document',
content: headings,
}
const options = {
renderNode: {
[BLOCKS.HEADING_2]:: (node, children) => {
return (
<li>{children}</li>
)
},
[BLOCKS.HEADING_3]:: (node, children) => {
return (
<li
style={{
marginLeft: `16px`,
}}
>{children}</li>
)
},
}
}
return (
<nav>
<h2>Table of contents</h2>
<ul>
{documentToReactComponents(document, options)}
</ul>
</nav>
)
}
TODO:
Some of the stuff that I'll add to this blog post:
- How to generate slug from each heading to create page navigation