PizzaHead
PizzaHead

Reputation: 968

How do I load a markdown file into a react component?

How would I load a .md markdown file into a react component? I have tried so many npm libraries through google searches and I cant find a good solution.

Code image

I want to load the .md file something like:

render() {
    <div>
        <MarkDown src="about.md" />
    </div>
}

Upvotes: 88

Views: 108156

Answers (15)

Green
Green

Reputation: 1741

1. Install react-markdown

npm i react-markdown

2. Use it in my react functional component

import { useEffect, useState } from 'react'
import ReactMarkdown from 'react-markdown'
import bookPath from './../Markdown/book.md'

const MyComponent = () => {
    const [text, setText] = useState('')

    useEffect(() => {
        fetch(bookPath)
        .then((response) => response.text())
        .then((md) => {
            setText(md)
        })
    }, [])

    return (
        <div>
            <ReactMarkdown children={text} />
        </div>
    )
}

export default MyComponent

Upvotes: 3

Jan
Jan

Reputation: 432

The Updated Version of https://stackoverflow.com/a/42928796/12271495

import React, { Component } from "react";
import { marked } from "marked";

class App extends Component {
  state = {markdown: ""}
  componentDidMount() {
    const readmePath = require("./blogs/README.md");

    fetch(readmePath)
      .then((response) => {
        return response.text();
      })
      .then((text) => {
        this.setState({
          markdown: marked.parse(text),
        });
      });
  }

  render() {
    const { markdown } = this.state;

    return (
      <section>
        <article dangerouslySetInnerHTML={{ __html: markdown }}></article>
      </section>
    );
  }
}

export default App;

Upvotes: 1

icc97
icc97

Reputation: 12783

Where I struggled with all these answers is the loading of the markdown file into frontend React. They make some kind of assumption as to what you're using.

For the world of React I would recommend MDX.

I'm using it with Next.js, but MDX is compatible with lots of Javascript bundlers and React libraries and does a lot of work to handle the bit that lots of people ignore here which is the loading in of non-JS syntax.

Note: MDX sticks more to the original style of Markdown without tables or the funky extras. If you want extra stuff you need to install MDX plugins

See these references:

Install with:

npm install @next/mdx @mdx-js/loader

next.config.js:

For Next.js integration we don't have to touch the webpack config just wrap our existing next.config.js with a withMDX function:

/**
 * @link https://github.com/vercel/next.js/tree/canary/packages/next-mdx#usage
 */
const withMDX = require('@next/mdx')()

// your existing next.config.js
const nextConfig = {
   ...
}

module.exports = withMDX(nextConfig)

about.mdx:

Read more about this in Using MDX docs

# Just some regular markdown

any old rubbish **will do**

You can stick extra <Funky /> stuff in here but *regular markdown* works fine

AboutPage.jsx:

Note below how now you don't need to do any fetching or useEffect, it's simply like loading in a JSON or JS file.

import React from "react";
import AboutMarkdown from "./about.mdx";

const AboutPage () => (
    <AboutMarkdown />
);

Upvotes: 5

ed94133
ed94133

Reputation: 1575

Another option is to put the Markdown in a .js file, using the backtick ` character to enclose the Markdown as an untagged template literal. Like this:

const MD = `
**TERMS OF SERVICE**

Last Modified: 30 November 2021...`

export default MD

Then you can import it like any other module.

Upvotes: 0

Thomas Sloan
Thomas Sloan

Reputation: 73

I wanted it to work using dynamic imports using react-markdown. My general code is below, you'll have to add a useEffect to call the function and put a reference to the state variable in the function return:

  const [displayElement, setDisplayElement] = useState(null);

  //Get markdown file
  const fetchMarkdown = async (location) => {
    console.log("MD location: ", location);
    try {
      //I figured out readmePath.default using print statements, left there in case
      //someone wants them
      const readmePath = await require("" + location);
      //console.log(readmePath);
      const response = await fetch(readmePath.default);
      //console.log("response => ", response);
      const text = await response.text();
      //console.log(text);

      // the state variable I am setting the markdown into, in my render function 
      // I have {displayElement}.
      setDisplayElement(
        <div className={styles.markdownContainer}>
          <ReactMarkdown children={text} />
        </div>
      );
    } catch (e) {
      console.log("Markdown file: couldn't read =>", location, e);
    }
  };

The addition of the empty string in const readmePath = await require("" + location); is required (hehe). I got that from here. I don't know why it works.

Upvotes: 0

Nishchit
Nishchit

Reputation: 19074

markdown-to-jsx provides very efficeint functionality to interact with markdown in React component.

It allows replacing/overriding of any HTML element with your Custom Component for markdown, here is the doc.

import React, { Component } from 'react'
import Markdown from 'markdown-to-jsx';
import README from './README.md'

class PageComponent extends Component {
  constructor(props) {
    super(props)

    this.state = { md: "" }
  }

  componentWillMount() {
    fetch(README)
      .then((res) => res.text())
      .then((md) => {
        this.setState({ md })
      })
  }

  render() {

    let { md } = this.state

    return (
      <div className="post">
        <Markdown children={md}/>
      </div>
    )
  }
}

export default PageComponent

Edit 2nd Aug'21

Functional Component
const PageComponent = ()=> {

    let [ content, setContent] = useState({md: ""});

    useEffect(()=> {
        fetch(README)
            .then((res) => res.text())
            .then((md) => {
                setContent({ md })
            })
    }, [])

    return (
      <div className="post">
        <Markdown children={content.md}/>
      </div>
    )
}

Upvotes: 16

Riunge Maina
Riunge Maina

Reputation: 542

Similar to @Xing-Han-Lu's answer but with react Markdown. The concept uses useEffect to load up the file then adds it to state using the useState hook where it's accessible to reactMarkdown

import React, { useState, useEffect } from "react";
import ReactMarkdown from "react-markdown";
import file from "./md/posts.md";

export default function () {
  const [markdown, setMarkdown] = useState("");

  useEffect(() => {
    fetch(file)
      .then((res) => res.text())
      .then((text) => setMarkdown(text));
  }, []);

  return (
    <>
      <ReactMarkdown source={markdown} />
    </>
  );
}

Upvotes: 18

Vlad
Vlad

Reputation: 6732

Approach using webpack loader

Install raw-loader

npm install raw-loader --save-dev

Update webpack.config.js

module.exports = {
  //...
  module: {
    rules: [
      // ...
      {
        test: /\.md$/,
        use: "raw-loader",
      },
    ],
  },
};

Create markdown file (say App.md)

# React & Markdown App

- Benefits of using React... but...
- Write layout in Markdown!

Import App.md and use it in React component.

import React from "react";
import ReactMarkdown from 'react-markdown';
import AppMarkdown from './App.md';

function App() {
  return (
    <div>
      <ReactMarkdown children={`${AppMarkdown}`} />
    </div>
  );
}

export default App;

Upvotes: 9

xhluca
xhluca

Reputation: 1016

I slightly modified this solution to use hooks and useEffect (which is different from componentWillUpdate but still works). If you built your app with create-react-app and you have a markdown document called document.md, you can build your app in the following way:

import { useState, useEffect } from 'react';
import Markdown from 'markdown-to-jsx';
import mdDocument from './document.md';

const App = () => {
  const [content, setContent] = useState("");

  useEffect(() => {
    fetch(mdDocument)
      .then(res => res.text())
      .then(md => { setContent(md) })
  })

  return (
    <div><Markdown children={content} /></div>
  )
}

export default App;

Upvotes: 1

Pramod Mali
Pramod Mali

Reputation: 1798

For Typescript + react please follow below steps:

  1. Create one type definition (index.d.ts) file in one of the project directory and put the following code.
declare module "*.md";
  1. Add tsconfig.json -> CompilerOptions -> typeRoots as following
{
     "compilerOptions": {
         ...
         "typeRoots": [ "<types-directory-created-in-#1>", "./node_modules/@types"],
         ...
     }
}
  1. Install two libraries showdown and html-react-parser

yarn add showdown or npm install showdown

yarn add html-react-parser or npm install html-react-parser

  1. In your component
import React, { useEffect, useState } from 'react';
import showdown from 'showdown';
import parse from 'html-react-parser';
import readme from 'path/filename.md';

export default function ComponentName() {
    const [html, setHTML] = useState("");

    //Use componentDidMount(): if class based component to load md file
    useEffect(() => {
        fetch(readme)
            .then(data => data.text())
            .then(text => {
                const converter = new showdown.Converter();
                setHTML(converter.makeHtml(text));
            })
    }, []);

    return (
        <div>{parse(html)}</div>
    )
}

Upvotes: 1

Otabek Butcher
Otabek Butcher

Reputation: 583

I have tried the above suggestions and deduced that after running a command

> npm install markdown
import ReactMarkdown from 'markdown';

it finally worked for me

Upvotes: -1

Kevin
Kevin

Reputation: 979

You should use react-markdown instead of the accepted answer, this solution doesn't use dangerouslySetInnerHTML.

App.js

import React, { Component } from 'react';
import AppMarkdown from './App.md';
import ReactMarkdown from 'react-markdown';

class App extends Component {

  constructor() {
    super();
    this.state = { markdown: '' };
  }

  componentWillMount() {
    // Get the contents from the Markdown file and put them in the React state, so we can reference it in render() below.
    fetch(AppMarkdown).then(res => res.text()).then(text => this.setState({ markdown: text }));
  }

  render() {
    const { markdown } = this.state;
    return <ReactMarkdown source={markdown} />;
  }
}

export default App;

App.md

# React & Markdown App
* Benefits of using React... but...
* Write layout in Markdown!

Upvotes: 36

James Donnelly
James Donnelly

Reputation: 128771

I use marked (GitHub).

I first import it like this:

import marked from "marked";

I then fetch my *.md file within React's componentDidMount event and store it in my component's state using marked(text) (where text is the response):

componentDidMount() {
  const readmePath = require("./Readme.md");

  fetch(readmePath)
    .then(response => {
      return response.text()
    })
    .then(text => {
      this.setState({
        markdown: marked(text)
      })
    })
}

...and finally I render it on the page using the dangerouslySetInnerHTML attribute:

render() {
  const { markdown } = this.state;

  return (
    <section>
      <article dangerouslySetInnerHTML={{__html: markdown}}></article>
    </section>
  )
}

Upvotes: 68

Caylan Van Larson
Caylan Van Larson

Reputation: 41

If you use Webpack (i.e. Electron React Boilerplate) then you can save a few steps by using Webpack loaders.

npm i -D html-loader markdown-loader marked

In webpack.config.renderer.dev.js:

import marked from 'marked';
const markdownRenderer = new marked.Renderer();

....

  // Markdown
  {
    test: /\.md$/,
    use: [
      {
        loader: 'html-loader'
      },
      {
        loader: 'markdown-loader',
        options: {
          pedantic: true,
          renderer: markdownRenderer
        }
      }
    ]
  }

Then, in the React component it's simply a require and setting the HTML.

import knownIssues from '../assets/md/known-issues.md';
....
<p dangerouslySetInnerHTML={{ __html: knownIssues }} />

Lastly, Flow will report an error (it still works) when importing the markdown file. Add this to .flowconfig to make Flow treat md files as string assets (care of Webpack):

module.name_mapper.extension='md' -> '<PROJECT_ROOT>/internals/flow/WebpackAsset.js.flow'

Upvotes: 0

Dorian
Dorian

Reputation: 23929

A full working example with react-markdown:

import React, { Component } from 'react'
import ReactMarkdown from 'react-markdown'
import termsFrPath from './Terms.fr.md'

class Terms extends Component {
  constructor(props) {
    super(props)

    this.state = { terms: null }
  }

  componentWillMount() {
    fetch(termsFrPath).then((response) => response.text()).then((text) => {
      this.setState({ terms: text })
    })
  }

  render() {
    return (
      <div className="content">
        <ReactMarkdown source={this.state.terms} />
      </div>
    )
  }
}

export default Terms

Upvotes: 77

Related Questions