Create a React Markdown Editor (Hooks & Context)

Video Tutorial

What is Markdown

Markdown is a lightweight markup language with plain-text-formatting syntax. It can be easily converted to different output formats but usually it gets converted to plain HTML.

Widely used for text formatting, presentations and text editors (for ex: Github README or issues).

What we are going to Create

So we will use the power of Markdown with React to create a next lever Markdown Editor which you type plain markdown text and it gets converted to HTML output.

We will be using ReactMarkdown library to convert markdown text to HTML and render it to the DOM with Hooks and React Context.

Here is the final application on codesandbox:

Cool! Let’s gets started.

Setting up React Project

We will use condesandbox for working on our project for ease and simplicity.

Let’s create a React project from the react template you will get a bare project with hello world example setup for you.

We will need some dependencies added alongside the React, React-dom and react-scripts libraries.

  • react-markdown: for parsing and converting plain markdown text to HTML and render to the DOM.

  • styled-components: for using CSS-in-JS and styling our components.

We also need to adjust some CSS style in order to make the Markdown editor fits the full width and height. So go under style.css and adjust it.

html, body, #root {
  width: 100%;
  height: 100%;
}

Editor Layout

The Editor layout is going to be very simple. split into two parts the left-hand part will be the area of where we put the markdown plain text and the right-hand area where we will see the converted output.

Inside of App.js we will create the main editor container.

import React from "react";
import "./styles.css";
import styled from "styled-components";

const AppContainer = styled.div`
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
`;

const Title = styled.div`
  font-size: 25px;
  font-weight: 700;
  font-family: "Lato", sans-serif;
  margin-bottom: 1em;
`;

const EditorContainer = styled.div`
  width: 100%;
  height: 100%;
  display: flex;
`;

We wrap our editor with the EditorContainer and for putting a simple header title we will use Title components.

export default function App() {
  return (
      <AppContainer>
        <Title>Markdown Editor</Title>
        <EditorContainer>
          <MarkedInput />
          <Result />
        </EditorContainer>
      </AppContainer>
  );
}

The MarkedInput component represents the text area where the Markdown plain text input goes in.

The Result component will be responsible for converting the Markdown plain text input into HTML and rendering it to the DOM.

Markdown Input Text Area

Let’s create the MarkedInput component where all the user inputs go in.

First, create a components folder under the src/ folder to put all of your application’s components in there. Create markedInput.jsx file inside the components folder.

The markedInput container will take half of the page’s full width so 50% of page width in order to split the editor into two halves one for input and the other for previewing the converted text.

import React, { useContext } from "react";
import styled from "styled-components";
import editorContext from "../editorContext";

const Container = styled.div`
  width: 50%;
  height: 100%;
  padding: 13px;
  border-right: 1.5px solid rgba(15, 15, 15, 0.4);
  font-family: "Lato", sans-serif;
`;

const Title = styled.div`
  font-size: 22px;
  font-weight: 600;
  margin-bottom: 1em;
  padding: 8px 0;
  border-bottom: 1px solid rgba(15, 15, 15, 0.3);
`;

The Container takes 50% of width and full page height.

Now, we need a text area to fit the size of the container for input.

const TextArea = styled.textarea`
  width: 100%;
  height: 100%;
  resize: none;
  border: none;
  outline: none;
  font-size: 17px;
`;

We removed the unwanted border and outline of the text area.

export function MarkedInput(props) {

  const onInputChange = e => {

  };

  return (
    <Container>
      <Title>Markdown Text</Title>
      <TextArea onChange={onInputChange} />
    </Container>
  );
}

We make sure to set onChange callback for the Text Area input change.

So in order to capture the input markdown from the text area and convert it from Markdown to HTML, we need to store it in a state and since we have the two components (MarkedInput and Result) need to access to the state we need to create the state in App parent component and pass it down to the components for a classical solution.

Alternatively, we would create a React Context to pass the state values and functions down to the child components without explicitly passing them through props.

Context provides a way to pass data through the component tree without having to pass props down manually at every level. React Docs

Let’s create an Editor Context inside of the root of src/ folder.

import React from "react";

const defaultContext = {
  markdownText: "",
  setMarkdownText: () => {}
};

export default React.createContext(defaultContext);

Now let’s Create a state variable using hooks and use EditorContext to pass it through the child tree from App.js (Root Application Component).

export default function App() {
  //Markdown Text State 
  const [markdownText, setMarkdownText] = useState("");
  //Context Value
  const contextValue = {
    markdownText,
    setMarkdownText
  };

  return (
    <EditorContext.Provider value={contextValue}>
      <AppContainer>
        <Title>Markdown Editor</Title>
        <EditorContainer>
          <MarkedInput />
          <Result />
        </EditorContainer>
      </AppContainer>
    </EditorContext.Provider>
  );
}

We create markdownText state using useState hook and create the context value from the state value and the function to update the state so we could use it to update the state value of the markDown text when user types something in the markedInput components.

React Context composes of two parts:

  • Provider: which takes the context value and must be at the root level where you want to use the context to pass props down the tree (at the root of the tree).

  • Consumer: consumes the provider in a child under the Provider tree (child of the provider) you can use useContext hook instead to access the context value instead.

Now let’s use the Editor Context in order to access setMarkdownText in MarkedInput component to update the text on input change.

export function MarkedInput(props) {
  const { setMarkdownText } = useContext(editorContext);

  const onInputChange = e => {
    const newValue = e.currentTarget.value;
    setMarkdownText(newValue);
  };

  return (
    <Container>
      <Title>Markdown Text</Title>
      <TextArea onChange={onInputChange} />
    </Container>
  );
}

We use useContext hook in order to get EditorContext Values.

We get the new text area value from event current target and we update the state with the new value.

Now the markdownText state variable will be updated once the user types something on the text area.

Converting Markdown to HTML

The Result component is responsible for taking the Markdown plain text and convert it to HTML and render it on the Preview Container we are going to use the help of react-markdown Component Library which going to do the heavy conversion of Markdown to HTML also Escaping the text for us so we could render the HTML safely.

Create a Result.jsx file in components folder.

import React, { useContext } from "react";
import styled from "styled-components";
import ReactMarkdown from "react-markdown";
import editorContext from "../editorContext";

const Container = styled.div`
  width: 50%;
  height: 100%;
  padding: 13px;
  font-family: "Lato", sans-serif;
`;

const Title = styled.div`
  font-size: 22px;
  font-weight: 600;
  margin-bottom: 1em;
  padding: 8px 0;
  border-bottom: 1px solid rgba(15, 15, 15, 0.3);
`;

const ResultArea = styled.div`
  width: 100%;
  height: 100%;
  border: none;
  font-size: 17px;
`;

It shares almost the same Container and Title components with MarkedInput component.

export function Result(props) {
  const { markdownText } = useContext(editorContext);

  return (
    <Container>
      <Title>Converted Text</Title>
      <ResultArea>
        <ReactMarkdown source={markdownText} />
      </ResultArea>
    </Container>
  );
}

We get the plain markdown text off the context and we simply provide to ReactMarkdown component through source prop and it will do the conversion for us behind the scenes.

The Result Container will take half of the width so we could get a perfect view between typing the markdown text and previewing it in the right-hand side.

And Voila! you got yourself a simple Markdown Editor from scratch How cool is that!

Check the Demo of the app on top or directly on Codesandbox.

No Comments Yet