Build a React JS Interactive Simple Interactive Component: A Basic Markdown Previewer

In the world of web development, the ability to display formatted text is crucial. Imagine you’re building a note-taking app, a blogging platform, or even a simple text editor. You’d want your users to write with basic formatting like headings, bold text, lists, and links. But how do you take plain text and transform it into something visually appealing and well-structured? The answer lies in Markdown, a lightweight markup language, and React, a powerful JavaScript library for building user interfaces. This tutorial will guide you through building a basic Markdown previewer in React, empowering you to convert Markdown syntax into rendered HTML on the fly.

Why Build a Markdown Previewer?

Markdown is a simple and widely used syntax for formatting text. It’s easy to read, write, and convert into HTML. A Markdown previewer allows users to see how their Markdown text will look when rendered as HTML, providing immediate feedback and making the writing process more efficient. This is especially helpful for:

  • Bloggers and Writers: Previewing how their posts will appear before publishing.
  • Note-takers: Formatting notes quickly and seeing the results instantly.
  • Developers: Displaying documentation or README files with formatted text.

Understanding the Basics: Markdown and React

Before diving into the code, let’s briefly touch upon Markdown and React.

Markdown

Markdown uses simple characters to format text. Here are a few examples:

  • # Heading 1 becomes <h1>Heading 1</h1>
  • **Bold text** becomes <strong>Bold text</strong>
  • *Italic text* becomes <em>Italic text</em>
  • - List item becomes <li>List item</li>
  • [Link text](url) becomes <a href=”url”>Link text</a>

There are many more Markdown syntaxes, but these will get you started.

React

React is a JavaScript library for building user interfaces. It uses a component-based architecture, meaning you build your UI from reusable components. These components manage their own state and render UI based on that state. React efficiently updates the DOM (Document Object Model) when the state changes, making your application dynamic and responsive.

Setting Up Your React Project

Let’s get started by creating a new React project using Create React App. Open your terminal and run the following commands:

npx create-react-app markdown-previewer
cd markdown-previewer

This will create a new React project named “markdown-previewer”. Navigate into the project directory.

Installing a Markdown Parser

To convert Markdown to HTML, we’ll use a Markdown parser. There are several options available. For this tutorial, we will use the “marked” library. Install it using npm or yarn:

npm install marked
# or
yarn add marked

“marked” is a popular and easy-to-use Markdown parser.

Building the Markdown Previewer Component

Now, let’s create the core component for our previewer. Open the `src/App.js` file and replace the existing code with the following:

import React, { useState } from 'react';
import { marked } from 'marked';

function App() {
  const [markdown, setMarkdown] = useState('');

  const handleChange = (e) => {
    setMarkdown(e.target.value);
  };

  const html = marked.parse(markdown);

  return (
    <div className="container">
      <h1>Markdown Previewer</h1>
      <div className="editor-container">
        <textarea
          id="editor"
          onChange={handleChange}
          value={markdown}
          placeholder="Enter Markdown here..."
        />
      </div>
      <div className="preview-container">
        <h2>Preview</h2>
        <div id="preview" dangerouslySetInnerHTML={{ __html: html }} />
      </div>
    </div>
  );
}

export default App;

Let’s break down this code:

  • Import Statements: We import `React`, `useState` (a React Hook for managing state), and `marked` (the Markdown parser).
  • State: We use `useState` to create a state variable called `markdown`. It holds the Markdown text entered by the user. The initial value is an empty string.
  • handleChange Function: This function is called whenever the user types in the textarea. It updates the `markdown` state with the new value from the textarea.
  • marked.parse(): This line calls the `marked.parse()` function, passing the `markdown` state as an argument. The `marked.parse()` function converts the Markdown text into HTML. The result is stored in the `html` variable.
  • JSX Structure: The component renders a `div` with class “container”. Inside this container:
    • An <h1> for the title.
    • A “editor-container” div that contains a <textarea> where the user enters Markdown. The `onChange` event of the textarea calls the `handleChange` function, which updates the state. The `value` prop is bound to the `markdown` state, so the textarea displays the current Markdown.
    • A “preview-container” div that displays the rendered HTML. The `dangerouslySetInnerHTML` prop is used to inject the HTML into the <div id=”preview”>. Important: Using `dangerouslySetInnerHTML` can be risky if you’re not careful about the source of the HTML. In this case, we control the source (the output of `marked.parse()`), so it’s safe.

Adding Styles (CSS)

To make our previewer look better, let’s add some basic CSS. Open the `src/App.css` file and add the following styles:

.container {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 20px;
  font-family: sans-serif;
}

.editor-container, .preview-container {
  width: 80%;
  margin-bottom: 20px;
}

textarea {
  width: 100%;
  height: 200px;
  padding: 10px;
  font-size: 16px;
  border: 1px solid #ccc;
  border-radius: 4px;
  resize: vertical;
}

#preview {
  border: 1px solid #ccc;
  padding: 10px;
  border-radius: 4px;
  background-color: #f9f9f9;
  text-align: left;
}

@media (min-width: 768px) {
  .container {
    flex-direction: row;
    justify-content: space-around;
  }

  .editor-container, .preview-container {
    width: 40%;
  }
}

These styles:

  • Center the content vertically on smaller screens.
  • Set the width of the editor and preview areas.
  • Style the textarea.
  • Style the preview area.
  • Use a media query to arrange the editor and preview side-by-side on larger screens.

Running the Application

Now, start your React development server:

npm start
# or
yarn start

This will open your Markdown previewer in your web browser. Type Markdown into the left textarea, and see the rendered HTML appear in the right preview area.

Handling Common Markdown Elements

Our basic previewer works, but let’s make it handle some common Markdown elements. Here’s how to incorporate different elements and common styling issues:

Headings

Markdown headings are created using the # symbol. For example:

# Heading 1
## Heading 2
### Heading 3

The `marked` library automatically converts these to HTML heading tags (<h1>, <h2>, <h3>, etc.). No additional code is needed.

Emphasis (Bold and Italics)

Use asterisks (*) or underscores (_) for emphasis:

**Bold text**
*Italic text*

The `marked` library automatically converts these to HTML <strong> and <em> tags.

Lists

Create unordered lists with dashes (-), asterisks (*), or plus signs (+):

- Item 1
- Item 2
- Item 3

Create ordered lists with numbers:

1. First item
2. Second item
3. Third item

The `marked` library handles lists correctly.

Links

Create links using the following format:

[Link text](https://www.example.com)

The `marked` library converts this to an <a> tag.

Images

Add images using this format:

![Alt text](image.jpg)

The `marked` library converts this to an <img> tag. Make sure the image file is accessible from your application’s perspective.

Code Blocks

Create code blocks using backticks (`) for inline code or triple backticks for multi-line code blocks.


`Inline code`

```javascript
function myfunction() {
  console.log('Hello, world!');
}
```

The `marked` library converts these to HTML <code> and <pre> tags. You might need to add CSS to style code blocks for better readability. Consider using a syntax highlighting library for more advanced code styling (see the “Advanced Features” section).

Advanced Features and Improvements

Here are some ways to enhance your Markdown previewer:

1. Syntax Highlighting

For code blocks, consider adding syntax highlighting. Libraries like Prism.js or highlight.js can automatically detect the programming language and apply appropriate styling to the code. This makes code blocks much more readable.

  1. Install a syntax highlighting library (e.g., `npm install prismjs`).
  2. Import the necessary CSS and JavaScript for your chosen library in your `src/App.js` or a separate component.
  3. Configure the library to automatically highlight code blocks. Often, this involves adding a class to the <pre> or <code> tags generated by `marked`. You can use a custom renderer in the `marked` configuration for this.

2. Live Preview with Delay

To avoid frequent re-renders while the user is typing, you can add a small delay before updating the preview. This improves performance and reduces flicker. Use the `setTimeout` and `clearTimeout` functions in JavaScript:

import React, { useState, useEffect } from 'react';
import { marked } from 'marked';

function App() {
  const [markdown, setMarkdown] = useState('');
  const [html, setHtml] = useState('');
  const [timeoutId, setTimeoutId] = useState(null);

  const handleChange = (e) => {
    const newMarkdown = e.target.value;
    setMarkdown(newMarkdown);

    if (timeoutId) {
      clearTimeout(timeoutId);
    }

    const id = setTimeout(() => {
      const parsedHtml = marked.parse(newMarkdown);
      setHtml(parsedHtml);
    }, 300); // Delay of 300 milliseconds

    setTimeoutId(id);
  };

  useEffect(() => {
    // Initial render
    const parsedHtml = marked.parse(markdown);
    setHtml(parsedHtml);
  }, [markdown]);

  return (
    <div className="container">
      <h1>Markdown Previewer</h1>
      <div className="editor-container">
        <textarea
          id="editor"
          onChange={handleChange}
          value={markdown}
          placeholder="Enter Markdown here..."
        />
      </div>
      <div className="preview-container">
        <h2>Preview</h2>
        <div id="preview" dangerouslySetInnerHTML={{ __html: html }} />
      </div>
    </div>
  );
}

export default App;

In this example:

  • We introduced `html` state to store the parsed HTML.
  • We introduced `timeoutId` state to store the ID of the timeout.
  • In `handleChange`, we clear any existing timeout before setting a new one.
  • We use `setTimeout` to delay the parsing.
  • The `useEffect` hook with `markdown` as a dependency ensures the HTML is updated initially and whenever the markdown changes.

3. Toolbar for Formatting

Add a toolbar with buttons for common Markdown formatting options (bold, italics, headings, lists, links, etc.). This makes the previewer more user-friendly, especially for users unfamiliar with Markdown syntax.

  1. Create a toolbar component: This component will contain the formatting buttons.
  2. Implement button click handlers: Each button should have a click handler that inserts the corresponding Markdown syntax into the textarea at the current cursor position. You can use JavaScript’s `selectionStart` and `selectionEnd` properties of the textarea to determine the cursor position and modify the text accordingly.
  3. Style the toolbar: Make the toolbar visually appealing and easy to use.

4. Error Handling

Implement error handling to gracefully handle invalid Markdown syntax or other potential issues. For example, you could display an error message if the `marked.parse()` function throws an error.

5. Customizable Styles

Allow users to customize the styles of the rendered HTML. This could involve providing options for:

  • Changing the font and font size.
  • Customizing the colors of headings, text, and backgrounds.
  • Providing different themes (light, dark, etc.).

Common Mistakes and Troubleshooting

Here are some common mistakes and how to fix them:

1. Markdown Not Rendering

Problem: The Markdown text isn’t being converted to HTML in the preview area.

Solution:

  • Check the `marked.parse()` function: Make sure you are calling `marked.parse(markdown)` correctly and that the result is being used to set the `__html` prop of the preview div.
  • Verify the state update: Ensure that the `handleChange` function correctly updates the `markdown` state whenever the user types in the textarea. Use `console.log(markdown)` inside `handleChange` to debug.
  • Inspect the HTML: Use your browser’s developer tools (right-click, “Inspect”) to examine the rendered HTML in the preview area. See if the HTML generated by `marked.parse()` is actually present.
  • Check for errors in the console: Look for any errors in the browser’s console that might indicate a problem with the `marked` library or your code.

2. Unescaped HTML

Problem: HTML tags entered directly in the textarea are not rendering correctly, or are displayed as plain text.

Solution: The `marked` library, by default, *escapes* HTML tags for security reasons. This means that < and > characters are converted to &lt; and &gt;, which are displayed literally. If you want to allow HTML in your Markdown, you can configure `marked` to not escape HTML. However, this can introduce security risks (cross-site scripting or XSS) if you are not careful about the source of the HTML. Here’s how to disable HTML escaping (use with caution!):

import React, { useState } from 'react';
import { marked } from 'marked';

function App() {
  const [markdown, setMarkdown] = useState('');

  marked.setOptions({
    mangle: false, // Disable automatic mangling of HTML tags
    headerIds: false, // Disable automatic generation of header IDs
    gfm: true, // Enable GitHub Flavored Markdown
    breaks: true, // Enable line breaks
    sanitize: false // Allow HTML (use with caution!)
  });

  const handleChange = (e) => {
    setMarkdown(e.target.value);
  };

  const html = marked.parse(markdown);

  return (
    <div className="container">
      <h1>Markdown Previewer</h1>
      <div className="editor-container">
        <textarea
          id="editor"
          onChange={handleChange}
          value={markdown}
          placeholder="Enter Markdown here..."
        />
      </div>
      <div className="preview-container">
        <h2>Preview</h2>
        <div id="preview" dangerouslySetInnerHTML={{ __html: html }} />
      </div>
    </div>
  );
}

export default App;

In this example, we set the `sanitize` option to `false` in `marked.setOptions()`. This tells `marked` to *not* sanitize (remove or escape) HTML tags. Be very careful with this setting, as it can allow malicious code to be injected into your previewer.

3. Styling Issues

Problem: The rendered HTML doesn’t look as expected (e.g., headings have the wrong font size, code blocks are not styled).

Solution:

  • Inspect the HTML: Use your browser’s developer tools to examine the HTML structure generated by `marked.parse()`. This will help you understand the HTML elements and classes that are being created.
  • Check your CSS: Make sure your CSS selectors target the correct HTML elements and classes. Use the browser’s developer tools to see which CSS rules are being applied to the elements in the preview area.
  • Specificity: Be aware of CSS specificity. If your CSS rules are not being applied, it might be because other, more specific rules are overriding them. Use more specific selectors or the `!important` rule (use sparingly) to override less specific rules.
  • External CSS: If you’re using an external CSS framework (e.g., Bootstrap, Tailwind CSS), make sure you’ve included it correctly in your project.
  • Syntax Highlighting: If you are using a syntax highlighting library, make sure you’ve correctly imported the CSS and JavaScript files, and that the library is configured to apply styles to the code blocks.

4. Performance Issues

Problem: The previewer lags or freezes when typing large amounts of Markdown text.

Solution:

  • Debouncing: Implement debouncing or throttling to limit the frequency of re-renders. See the “Live Preview with Delay” section above for an example of debouncing with `setTimeout`.
  • Performance Profiling: Use your browser’s developer tools to profile your application’s performance. This will help you identify any performance bottlenecks.
  • Optimize `marked.parse()`: While `marked.parse()` is generally fast, it can still be a bottleneck for very large Markdown documents. Consider optimizing your Markdown content or exploring alternative Markdown parsers if performance is critical.

Key Takeaways

  • You’ve learned how to build a basic Markdown previewer using React and the `marked` library.
  • You understand the core concepts of Markdown and how to convert it to HTML.
  • You’ve learned how to handle user input, update state, and render the output.
  • You’ve explored ways to enhance your previewer with features like syntax highlighting and live preview with delay.
  • You’ve learned how to troubleshoot common issues and improve performance.

FAQ

1. Can I use this previewer in a production environment?

Yes, you can. However, be mindful of security. If you allow users to enter HTML directly, sanitize the HTML to prevent XSS attacks. Otherwise, the basic previewer is suitable for many use cases.

2. How do I add a toolbar for formatting?

You’ll need to create a separate component for the toolbar. This component will contain buttons that, when clicked, insert Markdown syntax into the textarea at the current cursor position. You’ll need to use JavaScript’s `selectionStart` and `selectionEnd` properties to determine the cursor position and modify the text accordingly. Refer to the “Advanced Features” section for more details.

3. How can I customize the styles of the rendered HTML?

You can add CSS to style the HTML elements generated by the `marked` library. Consider providing options for users to choose different themes, fonts, and colors to customize the appearance of the preview area. You can use CSS variables to make it easier to change the styles. Again, see the “Advanced Features” section for details.

4. What are the alternatives to the “marked” library?

Other popular Markdown parsing libraries include:

  • Remark: A fast and extensible Markdown processor.
  • CommonMark: A library that adheres to the CommonMark specification for Markdown.
  • Showdown: Another well-established Markdown parser.

5. How do I deploy my Markdown Previewer?

You can deploy your React application to various platforms, such as Netlify, Vercel, or GitHub Pages. These platforms provide free hosting for static websites. You’ll typically build your React application using `npm run build` or `yarn build` and then deploy the contents of the `build` folder.

Building a Markdown previewer is a great way to learn about React, state management, and working with external libraries. It’s a practical project that can be adapted and expanded to suit your needs. The skills you gain from this tutorial—understanding how to handle user input, update the user interface dynamically, and integrate third-party libraries—are essential for building more complex React applications. Experiment with the code, add new features, and most importantly, have fun!