DEV Community

Cover image for Simple Markdown Previewer in ReactJS with Twin Macro and Styled Components.
Mohammed | 🇵🇰 🇵🇸
Mohammed | 🇵🇰 🇵🇸

Posted on

Simple Markdown Previewer in ReactJS with Twin Macro and Styled Components.

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Step 3: Importing important packages

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

import React, { useState } from 'react';
import styled from 'styled-components';
import tw from 'twin.macro';
import {marked} from 'marked';
import DOMPurify from 'dompurify';
Enter fullscreen mode Exit fullscreen mode
  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-componentsand twin.macro.

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`}`;

Enter fullscreen mode Exit fullscreen mode

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:

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

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

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

Enter fullscreen mode Exit fullscreen mode

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:

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>
);

Enter fullscreen mode Exit fullscreen mode

In this returnstatement, 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:

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;

Enter fullscreen mode Exit fullscreen mode

Here's the final product 🎉:

Image description

Top comments (0)