Create an automated Table of Contents with MDX

jahtx

James A. Hernandez

Posted on May 21, 2024

Create an automated Table of Contents with MDX

Using GatsbyJS, I have a blog which has posts summarized with a Table of Contents. The TOC should automatically pull any H2s from the content and be self-generated. Like so:

Image description

This code will take all H2 tags and assign anchor tags and summarize as a Table of Contents, as with this post.

// blogTemplate.tsx

  <MainLayout>
    <Container fluid="sm" >
          <Breadcrumb>
            <Breadcrumb.Item href="/">Home</Breadcrumb.Item>
            {frontmatter.status === 'portfolio' ? (
              <Breadcrumb.Item href="/portfolio">Portfolio</Breadcrumb.Item>
            ) : frontmatter.status === 'key-segment' ? null : (
              <Breadcrumb.Item href="/blog">Blog</Breadcrumb.Item>
            )}
            <Breadcrumb.Item active className="breadcrumb-truncate">
              {frontmatter.title}
            </Breadcrumb.Item>
          </Breadcrumb>
            <h1>{frontmatter.title}</h1>
            <div>
              <MDXProvider>{children}</MDXProvider>
            </div>
    </Container>
  </MainLayout>
Enter fullscreen mode Exit fullscreen mode

The first thing we would like to do is have automatic anchor tags placed on our H2 headings. We do this by taking advantage of MDX and creating a react component that will envelop our H2 in our MDX document.

This is the component:

// H2ThemeWrapper.tsx

function getAnchor(text: string) {
  return text
    .toLowerCase()
    .replace(/[^a-z0-9 ]/g, '')
    .replace(/[ ]/g, '-');
}
const H2ThemeWrapper = ({ children }) => {
  const anchor = getAnchor(children);
  const link = `#${anchor}`;
  return (
    <h2 className="remark__h2 mt-4" id={anchor}>
      <a href={link}>ยง &nbsp;</a>
      {children}
    </h2>
  );
};
export default H2ThemeWrapper;

Enter fullscreen mode Exit fullscreen mode

And this is how it's used in our MDX document:

// index.md
---
date: '2023-10-08'
title: 'Create an automated Table of Contents with MDX'
tags: ['gatsby', 'react', 'markdown', 'mdx']
slug: table-of-contents-mdx
featuredImage: toc.jpg
author: James A. Hernandez
status: published
showTOC: true
prismThemes: ['prism-coldark-dark', 'prism-ghcolors']
description: "Within blog posts, I have a need to summarize the post by H2 tags, which I don't wish to do manually. This code will take all H2 tags and assign anchor tags and summarize as a Table of Contents, as with this post."
---

import H2ThemeWrapper from 'wrappers/H2ThemeWrapper';


<H2ThemeWrapper>Using an MDX Template</H2ThemeWrapper>
Enter fullscreen mode Exit fullscreen mode

Creating the TOC

Once we have the anchor tags automated, now we move on to the task of creating the TOC. We'll create a useEffect in our template to pull all the H2s from Markdown (in this case all H2s using the class remark__h2).

// blogTemplate.tsx
  const [tableOfContents, setTableOfContents] = useState([]);

  useEffect(() => {
    const headings = Array.from(document.querySelectorAll('.remark__h2')).map(
      (heading) => {
        const anchor = heading.id;
        const title = heading.textContent.replace('ยง', '').trim(); // Remove the initial link symbol
        return { title, anchor };
      },
    );
    setTableOfContents(headings);
  }, []);
Enter fullscreen mode Exit fullscreen mode

A separate component is used to stylize our TOC when it's created:

// TableOfContents.tsx

interface TableOfContentsProps {
  tableOfContents: { title: string; anchor: string }[];
}

const TableOfContents: React.FC<TableOfContentsProps> = ({
  tableOfContents,
}) => {
  return (
    <div className="tableOfContents mt-4 rounded">
      <h2>Table of Contents</h2>
      <ul>
        {tableOfContents.map((item) => (
          <li key={item.anchor}>
            <a href={`#${item.anchor}`}>{item.title}</a>
          </li>
        ))}
      </ul>
    </div>
  );
};

export default TableOfContents;
Enter fullscreen mode Exit fullscreen mode

And now our TOC can be inserted in the blog template, as such (in this case, conditional on a boolean if you so wish):

    {frontmatter.showTOC && (
      <TableOfContents tableOfContents={tableOfContents} />
    )}
Enter fullscreen mode Exit fullscreen mode
๐Ÿ’– ๐Ÿ’ช ๐Ÿ™… ๐Ÿšฉ
jahtx
James A. Hernandez

Posted on May 21, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

ยฉ TheLazy.dev

About