React-markdown: Table of contents links

Created on 25 Mar 2020  路  6Comments  路  Source: remarkjs/react-markdown

I have some markup

1. [Top Level](#Top-Level)
   1. [Windows](#Windows)
   2. [OSX/Linux](#OSX/Linux)
   3. [Android](#Android)

## Top Level

## Windows

## OSX/Linux

## Android

In markup the table of conents links to the headers. The HTML generated creates the right links

<a href="#Windows">Windows</a>

but the headings aren't anchored. If they were

<h1><a >Windows</a></h1>

It would work. Is there a way of doing this?
```

馃憖 areexternal 馃檵 typquestion

Most helpful comment

you would need to add an id to each one of the headers in order for the correct links to be generated.

I replaced my heading node in the renderers prop with HeadingRenderer

import React from 'react';

const flatten = (text: string, child) => {
  return typeof child === 'string'
    ? text + child
    : React.Children.toArray(child.props.children).reduce(flatten, text);
};

/**
 * HeadingRenderer is a custom renderer
 * It parses the heading and attaches an id to it to be used as an anchor
 */
const HeadingRenderer = props => {
  const children = React.Children.toArray(props.children);
  const text = children.reduce(flatten, '');
  const slug = text.toLowerCase().replace(/\W/g, '-');
  return React.createElement('h' + props.level, { id: slug }, props.children);
};

export default HeadingRenderer;

All 6 comments

you would need to add an id to each one of the headers in order for the correct links to be generated.

I replaced my heading node in the renderers prop with HeadingRenderer

import React from 'react';

const flatten = (text: string, child) => {
  return typeof child === 'string'
    ? text + child
    : React.Children.toArray(child.props.children).reduce(flatten, text);
};

/**
 * HeadingRenderer is a custom renderer
 * It parses the heading and attaches an id to it to be used as an anchor
 */
const HeadingRenderer = props => {
  const children = React.Children.toArray(props.children);
  const text = children.reduce(flatten, '');
  const slug = text.toLowerCase().replace(/\W/g, '-');
  return React.createElement('h' + props.level, { id: slug }, props.children);
};

export default HeadingRenderer;

So I should use

<ReactMarkdown
  source={markdown}
  escapeHtml={false}
  renderers={{'heading',HeadingRenderer}}
/>

So I should use

<ReactMarkdown
  source={markdown}
  escapeHtml={false}
  renderers={{'heading',HeadingRenderer}}
/>

No, it would be renderers={{ heading: HeadingRenderer}}

Thank you very much - had to remove toLowerCase() on line 16, but after doing that it works perfectly

@davidsteed another approach could be to use a plugin to automatically add anchors to headings, like https://github.com/remarkjs/remark-slug
or even generate the entire table of contents automatically with a plugin using: https://github.com/remarkjs/remark-toc

you would need to add an id to each one of the headers in order for the correct links to be generated.

I replaced my heading node in the renderers prop with HeadingRenderer

import React from 'react';

const flatten = (text: string, child) => {
  return typeof child === 'string'
    ? text + child
    : React.Children.toArray(child.props.children).reduce(flatten, text);
};

/**
 * HeadingRenderer is a custom renderer
 * It parses the heading and attaches an id to it to be used as an anchor
 */
const HeadingRenderer = props => {
  const children = React.Children.toArray(props.children);
  const text = children.reduce(flatten, '');
  const slug = text.toLowerCase().replace(/\W/g, '-');
  return React.createElement('h' + props.level, { id: slug }, props.children);
};

export default HeadingRenderer;

Thank you very much!

Was this page helpful?
0 / 5 - 0 ratings