Simple Markdown Previewer in ReactJS with Twin Macro and Styled Components.
Tags: webdev, react, javascript, tutorial
Published: 2023-09-13 17:21:41

Hello folks! Ever wanted to build your own Markdown Previewer using ReactJS? Well, today is your lucky day! I recently built one using ReactJS, Twin Macro, and styled-components, and I thought it would be fun to create a step-by-step guide to help you build one too. So, grab a cup of coffee (or tea, if that's your thing), and let's get started!

## Step 1: Setting Up the Basic React App

First things first, we need to set up a basic React app. If you haven't already installed Node.js and npm, make sure to install them before proceeding.

Open your terminal and run the following command to create a new React app:

```
npx create-react-app markdown-previewer
```

## Step 2: Installing Packages

Next, we need to install the necessary packages. Navigate to your project folder and run the following commands to install the required packages:

```
npm install styled-components twin.macro marked dompurify
```

## Step 3: Importing important packages

Now that we have our packages installed, let's understand the import statements in our code.

```javascript
import React, { useState } from 'react';
import styled from 'styled-components';
import tw from 'twin.macro';
import {marked} from 'marked';
import DOMPurify from 'dompurify';
```

1. **React, { useState }** are imported from the **'react'** package. The **useState** hook is used to manage the state in our functional component.
2. **styled** is imported from the **'styled-components'** package. This allows us to write actual CSS code to style our components.
3. **tw** is imported from **'twin.macro'**. This is a utility-first CSS framework for rapidly building custom designs.
4. **marked** is imported from the **'marked'** package. This is a full-featured markdown parser and compiler.
5. **DOMPurify** is imported from the **'dompurify'** package. This is used to sanitize our HTML and prevent XSS attacks.

## Step 4: Styling Our Components

Now that we have our import statements ready, let's style our components using `styled-components `and `twin.macro`.

```typeScript
const Container = styled.div`
  ${tw`container mx-auto mt-10 mb-20`}
`;

const Header = styled.div`
  ${tw`text-center text-xl font-bold`}
`;

const Row = styled.div`
  ${tw`flex mt-10`}
`;

const Column = styled.div`
  ${tw`flex-1 mx-5 h-[35rem] w-[35rem]`}
`;

const Textarea = styled.textarea`
  ${tw`w-full h-full p-3 border rounded-md transition-all duration-300 ease-in-out`}
  &:focus {
    ${tw`border-blue-500 outline-none shadow-outline`}
  }
`;
const Preview = styled.div`
  ${tw`h-full w-full overflow-y-scroll overflow-x-scroll p-3 border rounded-md bg-gray-100`}`;

```

Here, we have defined a **Container**, **Header**, **Row**, **Column**, **Textarea**, and **Preview** using `styled-components` and `twin.macro`. The **Container** is a `div` element with some margin and padding. The **Header** is a `div` element with centered text and a bold font. The **Row** is a `div` element with flex display and some margin at the top. The **Column** is a `div` element with flex-1, margin, height, and width defined. The **Textarea** is a `textarea` element with full width, full height, padding, border, rounded corners, and transition effects. The **Preview** is a `div` element with full height, full width, overflow scroll, padding, border, rounded corners, and a gray background.

## Step 5: Handling Markdown Input and Preview

Now that we have our components styled, let's handle the markdown input and preview:

```javaScript
const [markdown, setMarkdown] = useState('');

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

const getSanitizedHTML = () => {
  const rawHTML = marked(markdown);
  return DOMPurify.sanitize(rawHTML);
}

```

In this block of code, we have defined a `markdown` state and a `setMarkdown` function to update the state. The `handleMarkdownChange` function is used to update the markdown state whenever the user types in the `Textarea`. The `getSanitizedHTML` function is used to convert the markdown text to HTML and sanitize it using `DOMPurify` to prevent XSS attacks.

## Step 6: Rendering the Components

Finally, let's render our components. Here is the return statement for our component:

```react
return (
  <Container>
    <Header>Markdown Previewer</Header>

    <Row>
      <Column>
        <Header>Markdown Input</Header>
        <Textarea value={markdown} onChange={handleMarkdownChange} />
      </Column>

      <Column>
        <Header>Preview</Header>
        <Preview dangerouslySetInnerHTML={{ __html: getSanitizedHTML()}} />
      </Column>
    </Row>
  </Container>
);

```

In this `return `statement, we have a **Container** with a **Header**, a **Row** with two **Columns**, each with a **Header**, a **Textarea** for the markdown input, and a **Preview** for the markdown preview. The **Textarea** uses the markdown state as its value and the `handleMarkdownChange` function as its `onChange` handler. The **Preview** uses the `getSanitizedHTML` function to set its `innerHTML`.


And that's it! You have successfully built a Markdown Previewer using ReactJS, Twin Macro, and styled-components. Congratulations! 🎉.

And here's the complete component code:

```react
import React, { useState } from 'react';
import styled from 'styled-components';
import tw from 'twin.macro';
import {marked} from 'marked';
import DOMPurify from 'dompurify';


const Container = styled.div`
  ${tw`container mx-auto mt-10 mb-20`}
`;

const Header = styled.div`
  ${tw`text-center text-xl font-bold`}
`;

const Row = styled.div`
  ${tw`flex mt-10`}
`;

const Column = styled.div`
  ${tw`flex-1 mx-5 h-[35rem] w-[35rem]`}
`;

const Textarea = styled.textarea`
  ${tw`w-full h-full p-3 border rounded-md transition-all duration-300 ease-in-out`}
  &:focus {
    ${tw`border-blue-500 outline-none shadow-outline`}
  }
`;

const Preview = styled.div`
  ${tw`h-full w-full overflow-y-scroll overflow-x-scroll p-3 border rounded-md bg-gray-100`}
  
`;

const MarkdownPreviewer = () => {
  const [markdown, setMarkdown] = useState('');

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

  const getSanitizedHTML = () => {
    const rawHTML = marked(markdown);
    return DOMPurify.sanitize(rawHTML);
  }

  return (
    <Container>
      <Header>Markdown Previewer</Header>

      <Row>
        <Column>
          <Header>Markdown Input</Header>
          <Textarea value={markdown} onChange={handleMarkdownChange} />
        </Column>

        <Column>
          <Header>Preview</Header>
          <Preview dangerouslySetInnerHTML={{ __html: getSanitizedHTML()}} />
        </Column>
      </Row>
    </Container>
  );
};

export default MarkdownPreviewer;

```

Here's the final product 🎉:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kwby72saa26jx12qc2g8.png)