Creating a Markdown Editor in React.js & TypeScript with Deployment through Github Actions
Tags: react, typescript, webdev, beginners
Published: 2020-03-03 05:17:14

---
title: Creating a Markdown Editor in React.js & TypeScript with Deployment through Github Actions
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/bskfkz7hrrnsjvm1jlyo.png
published: true
description: This contains the article about creating markdown editor with the use of React.js and TypeScript with combining continuous deployments using Github actions workflow
tags: reactjs, typescript, webdev, beginner
---

I remember when I was doing freeCodeCamp, I was about to make a Markdown editor in one of the projects. So I decided to go with the Markdown editor this time, combined with React.js and TypeScript.

## What you'll learn

- Setting up React.js project with TypeScript
- Creating a markdown editor by compiling down it to html
- Using React hooks to create theming for the application
- Continuous deployments through Github Actions

I am a lazy person, I think most of you are, too. So here's the code and demo link, if you directly want to see them.

### Project Source Code:
{% github https://github.com/ashwamegh/react-typescript-markdown-editor no-readme %}

### Project Demo: [ashwamegh/react-typescript-markdown-editor](https://ashwamegh.github.io/react-typescript-markdown-editor/)

Lets start with setting up our project

## 1. Setting up our project with React.js & TypeScript

We all know the capabilities of TypeScript, how it can save the day for your silly mistakes. And if combined with react, they both become a great combination to power any application.

I will be using `create-react-app` since, it gives the TypeScript support out of the box. Go to your root directory where you want to create the project and run this command:

```shell
npx create-react-app markdown-editor --template typescript

```
this `--template typescript` flag will do all the hard work for you, setting up React.js project with TypeScript.

Later, you'll need to remove some of bootstrapped code to start creating your application.

For reference you can check this initial commit to see what has been removed: 
`https://github.com/ashwamegh/react-typescript-markdown-editor/commit/7cc379ec0d01f3f1a07396ff2ac6c170785df57b`


After you've completed initial steps, finally we'll be moving on to creating our Markdown Editor.


## 2. Creating Markdown Editor

Before diving into the code, let's see the folder structure for our project, which we will be developing.

```shell
├── README.md
├── package.json
├── public
|  ├── favicon.ico
|  ├── index.html
|  ├── logo192.png
|  ├── logo512.png
|  ├── manifest.json
|  └── robots.txt
├── src
|  ├── App.test.tsx
|  ├── App.tsx
|  ├── components
|  |  ├── Editor.tsx
|  |  ├── Footer.tsx
|  |  ├── Header.tsx
|  |  ├── Main.tsx
|  |  ├── Preview.tsx
|  |  └── shared
|  |     └── index.tsx
|  ├── index.css
|  ├── index.tsx
|  ├── react-app-env.d.ts
|  ├── serviceWorker.ts
|  ├── setupTests.ts
|  └── userDarkMode.js
├── tsconfig.json
└── yarn.lock
```

I will be using `emotion` for creating styles for my components and `react-icons` for icons used in the project. So you'll be needed to install `emotion` and `react-icons` by running this command:
```shell
npm i -S @emotion/core @emotion/styled react-icons
```
or if you're using `yarn` like me, you can run

```shell 
yarn add @emotion/core @emotion/styled react-icons
```

Moving forward, first of we will create a `shared` components folder to create components we will be reusing.

```tsx

/* src/components/shared/index.tsx */

import React from 'react'
import styled from '@emotion/styled'

export const ColumnFlex = styled.div`
  display: flex;
  flex-direction: column;
`
export const RowFlex = styled.div`
  display: flex;
  flex-direction: row;
`

```

> In this file, we have declared two styled components for `flex-column` and `flex-row` styled `divs` which we'll be using later.
To know more about `styled-components` with `emotion` library, head on to this [link](https://emotion.sh/docs/styled).

## 3 Using React hooks to create a custom theme hook

We'll use react hooks to create our custom hook to implement basic theming capabilities, using which we can toggle our theme from light to dark colors.

```js
/* useDarMode.js */

import { useEffect, useState } from 'react'

export default () => {
  const [theme, setTheme] = useState('light')

  const toggleTheme = () => {
    if (theme === 'dark') {
      setTheme('light')
    } else {
      setTheme('dark')
    }
  }

  useEffect(() => {
    const localTheme = localStorage.getItem('theme')
    if (localTheme) {
      setTheme(localTheme)
    }
  }, [])

  return {
    theme,
    toggleTheme,
  }
}
```

> In our hooks file, we are setting the initial state of the theme to be `light` using `useState` hook. And using `useEffect` to check whether any theme item exists in our browser's local storage, and if there is one, pick the theme from there and set it for our application.


Since, we have defined our shared components and custom react hook for theming, let's dive into our app components.

So, I have divided our app structure into 5 components and those are: Header, Main (contains main section of the app with Editor & Preview component) and Footer component.

1. Header // contains normal header code and a switch to toggle theme
2. Main // container for Editor and Preview components
 i. Editor // contains code for Editor 
 ii. Preview // contains code for previewing markdown code into HTML
3. Footer // contains normal footer code


```tsx
/* src/components/Header.tsx */

import React from 'react'
import { FiSun } from 'react-icons/fi'
import { FaMoon } from 'react-icons/fa'

// this comment tells babel to convert jsx to calls to a function called jsx instead of React.createElement
/** @jsx jsx */
import { css, jsx } from '@emotion/core'

// Prop check in typescript
interface Props {
  toggleTheme: () => void,
  theme: string
}

const Header: React.FC<Props> = ({ theme, toggleTheme }) => {

  return (
    <header
      css={theme === 'dark' ?
      css`
        display: flex;
        flex-direction: row;
        justify-content: space-between;
        background-color: #f89541;
        padding: 24px 32px;
        font-size: 16px;
      `:css`
        display: flex;
        flex-direction: row;
        justify-content: space-between;
        background-color: #f8f541;
        padding: 24px 32px;
        box-shadow: 0px -2px 8px #000;
        font-size: 16px;
    `}>
      <div className="header-title">
        Markdown Editor
      </div>
      <div css={
        css`
          cursor: pointer;
        `}
        onClick={toggleTheme}
      >
       {
         theme === 'dark'?
         <FaMoon />:
         <FiSun />
       }
      </div>
    </header>
  )
}

export default Header;
```

> In this component, we are using TypeScript for prop checks and you may wonder, why we're mentioning `React.FC` here. Its just that, by typing our component as an FC (FunctionComponent), the React TypeScripts types allow us to handle children and defaultProps correctly.

For styling our components we're using `css` prop with string styles from `emotion` library, you can learn more about this by following the docs [here](https://emotion.sh/docs/css-prop#string-styles)

After creating the Header component, we'll create our Footer component and then we'll move on to Main component.

Let's see the code for Footer component

```tsx
import React from 'react'

// this comment tells babel to convert jsx to calls to a function called jsx instead of React.createElement
/** @jsx jsx */
import { css, jsx } from '@emotion/core'

const Footer: React.FC = () => {

  return (
    <footer>
      <div 
        className="footer-description"
        css={
            css`
                padding: 16px 0px;
                overflow: hidden;
                position: absolute;
                width: 100%;
                text-align: center;
                bottom: 0px;
                color: #f89541;
                background: #000;
            `
        }>
       <span>{`</>`}</span><span> with <a href="https://reactjs.org" target="_blank">React.js</a> &amp; <a href="https://www.typescriptlang.org/" target="_blank">TypeScript</a></span>
      </div>
    </footer>
  )
}

export default Footer;

```

Footer component contains simple code to render usual credit stuff.

```tsx
/* src/components/Main.tsx */

import React, { useState } from 'react'

// this comment tells babel to convert jsx to calls to a function called jsx instead of React.createElement
/** @jsx jsx */
import { css, jsx } from '@emotion/core'
import { RowFlex } from './shared'
import Editor from './Editor';
import Preview from './Preview';

interface Props {
  theme: string
}

const Main: React.FC<Props> =  ({ theme }) => {
  const [markdownContent, setMarkdownContent] = useState<string>(`
# H1
## H2
### H3
#### H4
##### H5

__bold__
**bold**
_italic_
`);
  return (
    <RowFlex
      css={css`
        padding: 32px;
        padding-top: 0px;
        height: calc(100vh - 170px);
        `}>
      <Editor theme={theme} markdownContent={markdownContent} setMarkdownContent={setMarkdownContent}/>
      <Preview theme={theme} markdownContent={markdownContent}/>
    </RowFlex>
  )
}

export default Main;

```

Since, some of the code will look familiar to you from the previous components which you can now understand yourself. Other than that, we have used `useState` hook to create a state to hold our markdown content and a handler to set it, called `setMarkdownContent` in the code.

> We need to pass these down to our `Editor` and `Preview` components, so that, they can provide the user the way to edit and preview their markdown content. We have also set an initial state for content with some basic markdown text.


Let's see the code for Editor component:


```tsx
/* src/components/Editor.tsx */

import React, { ChangeEvent } from 'react'
import PropTypes from 'prop-types';

// this comment tells babel to convert jsx to calls to a function called jsx instead of React.createElement
/** @jsx jsx */
import { css, jsx } from '@emotion/core'
import { ColumnFlex } from './shared'

interface Props {
  markdownContent: string;
  setMarkdownContent: (value: string) => void,
  theme: string
}

const Editor: React.FC<Props> = ({ markdownContent, setMarkdownContent, theme }) => {
    return (
        <ColumnFlex
        id="editor"
        css={css`
            flex: 1;
            padding: 16px;
          `}>
        <h2>
        Editor
        </h2>
        <textarea
          onChange={(e: ChangeEvent<HTMLTextAreaElement>) => setMarkdownContent(e.target.value)}
          css={theme === 'dark'?
          css`
            height: 100%;
            border-radius: 4px;
            border: none;
            box-shadow: 0 -2px 10px rgba(0, 0, 0, 1);
            background: #000;
            color: #fff;
            font-size: 100%;
            line-height: inherit;
            padding: 8px 16px;
            resize: none;
            overflow: auto;
            &:focus {
              outline: none;
            }
          `
          : css`
            height: 100%;
            border-radius: 4px;
            border: none;
            box-shadow: 2px 2px 10px #999;
            font-size: 100%;
            line-height: inherit;
            padding: 8px 16px;
            resize: none;
            overflow: auto;
            &:focus {
              outline: none;
            }
          `}
          rows={9}
          value={markdownContent}
          />
      </ColumnFlex>
    )
}

Editor.propTypes = {
  markdownContent: PropTypes.string.isRequired,
  setMarkdownContent: PropTypes.func.isRequired,
}

export default Editor;
```

> This is a straight forward component which uses `<textarea/>` to provide the user a way to enter their inputs, which have to be further compiled down to render it as HTML content in the Preview component.


Now, we have created almost all the components to hold our code except the Preview component.
We'll need something to compile down the user's markdown content to simple HTML, and we don't want to write all the compiler code, because we have plenty of options to choose from.

In this application, we'll be using **`marked`** library to compile down our markdown content to HTML. So you will be need to install that, by running this command:

```shell
npm i -S marked
```

or with yarn

```shell
yarn add marked
```

> If you want to know more about this library, you can see it [here](https://github.com/markedjs/marked)

Let's see the code for our Preview component

```tsx
/* src/components/Preview.tsx */

import React from 'react'
import PropTypes from 'prop-types'
import marked from 'marked'

// this comment tells babel to convert jsx to calls to a function called jsx instead of React.createElement
/** @jsx jsx */
import { css, jsx } from '@emotion/core'
import { ColumnFlex } from './shared'

interface Props {
    markdownContent: string,
    theme: string
}

const Preview: React.FC<Props> = ({ markdownContent, theme }) => {
    const mardownFormattedContent = ( marked(markdownContent));

    return (
        <ColumnFlex
            id="preview"
            css={css`
            flex: 1;
            padding: 16px;
            `}
        >
            <h2>Preview</h2>
            <div
                css={theme === 'dark'
                ? css`
                height: 100%;
                border-radius: 4px;
                border: none;
                box-shadow: 0 -2px 10px rgba(0, 0, 0, 1);
                font-size: 100%;
                line-height: inherit;
                overflow: auto;
                background: #000;
                padding: 8px 16px;
                color: #fff;
                `
                : css`
                height: 100%;
                border-radius: 4px;
                border: none;
                box-shadow: 2px 2px 10px #999;
                font-size: 100%;
                line-height: inherit;
                overflow: auto;
                background: #fff;
                padding: 8px 16px;
                color: #000;
            `}
            dangerouslySetInnerHTML={{__html: mardownFormattedContent}}
            >
            </div>
        </ColumnFlex>
    )
}

Preview.propTypes = {
    markdownContent: PropTypes.string.isRequired
  }

export default Preview;
```

> In this component, we are compiling the markdown content and storing it in  mardownFormattedContent variable. And to show a preview of the content in HTML, we will have to use `dangerouslySetInnerHTML` prop to display the HTML content directly into our DOM, which we are doing by adding this `dangerouslySetInnerHTML={{__html: mardownFormattedContent}}` prop for the div element.

Finally we'are ready with all the component which will be need to create our Markdown editor application. Let's bring all of them in our `App.tsx` file.

```tsx
/* src/App.tsx */

import React from 'react'
import { css, jsx } from '@emotion/core'

// Components
import Header from './components/Header'
import Main from './components/Main'
import Footer from './components/Footer';
import useDarkMode from './userDarkMode';

function App() {
  const { theme, toggleTheme } = useDarkMode();
  const themeStyles = theme === 'light'? {
    backgroundColor: '#eee',
    color: '#000'
  }: {
    backgroundColor: '#171616',
    color: '#fff'
  }
  return (
    <div 
      className="App"
      style={themeStyles}
      >
      <Header theme={theme} toggleTheme={toggleTheme}/>
      <Main theme={theme}/>
      <Footer />
    </div>
  );
}

export default App;

```

In our App component, we are importing the child components and passing down the theme props.

Now, if you have followed all steps above, you'll have a running markdown editor application, for styles I have used, you can see my source code using then link I mentioned.


> Now, its time to create Github actions for our project to create continuous deployment workflow on every push to master.


## 4 Setting up continuous deployments through Github Actions


We’ll be using Github actions workflow to build and deploy our web application on every push to master.

> Since this is not a enterprise application that holds the branches for production and development, I will setup my workflow for master branch, but if in any time in future, you require to setup the Github action workflow for your enterprise application, Just be careful with the branches.

To do so, we’ll follow some steps:
1. Create a folder in our project root directory `.github/workflows/`, this will hold all the  workflows config.
2. We’ll be using [`JamesIves/github-pages-deploy-action`](https://github.com/JamesIves/github-pages-deploy-action) action to deploy our application.
3. Next we’ll create our `.yml` file here, which will be responsible for the action to build and deploy our application to GitHub pages. Let’s name it `build-and-deploy-to-gh-pages.yml`

Let's see what goes inside this `build-and-deploy-to-gh-pages.yml`

```yml
# build-and-deploy-to-gh-pages.yml

name: Build & deploy to GitHub Pages
on:
  push:
    branches:
      - master
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v1
      - name: Set up Node
        uses: actions/setup-node@v1
        with:
          node-version: 10.x 
      - name: Set email
        run: git config --global user.email "${{ secrets.adminemail }}"
      - name: Set username
        run: git config --global user.name "${{ secrets.adminname }}"
      - name: npm install command
        run: npm install
      - name: Run build command
        run: npm run build
      - name: Deploy
        uses: JamesIves/github-pages-deploy-action@releases/v3
        with:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          BASE_BRANCH: master
          BRANCH: gh-pages # The branch the action should deploy to.
          FOLDER: build # The folder the action should deploy.
```

This workflow will run every time, we push something into master and will deploy the application through `gh-pages` branch.

Let's Breakdown the Workflow file

```yml
name: Build & deploy to GitHub Pages
on:
  push:
    branches:
      - master
```

This defines our workflow name and trigger for running the jobs inside it. Here we are setting the trigger to listen to any `Push` events in `master` branch.

```yml
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v1
      - name: Set up Node
        uses: actions/setup-node@v1
        with:
          node-version: 10.x 
      - name: Set email
        run: git config --global user.email "${{ secrets.adminemail }}"
      - name: Set username
        run: git config --global user.name "${{ secrets.adminname }}"
      - name: npm install command
        run: npm install
      - name: Run build command
        run: npm run build
      - name: Deploy
        uses: JamesIves/github-pages-deploy-action@releases/v3
        with:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          BASE_BRANCH: master
          BRANCH: gh-pages # The branch the action should deploy to.
          FOLDER: build # The folder the action should deploy.
```

This is the most important part in our workflow, which declares the `jobs` to be done. Some of the lines in the config are self-explanatory `runs-on: ubuntu-latest` it defines the system, it will be running on. 

```yml
- name: Checkout
        uses: actions/checkout@v1
```

This is an action for checking out a repo, and in later jobs we are setting our development environment by installing node and setting our git profile config. Then we are running `npm install` to pull out all the dependencies and finally running the `build` command.

```yml
- name: Deploy
        uses: JamesIves/github-pages-deploy-action@releases/v3
        with:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          BASE_BRANCH: master
          BRANCH: gh-pages # The branch the action should deploy to.
          FOLDER: build # The folder the action should deploy.
```
> After the build command has been completed, we are using `JamesIves/github-pages-deploy-action@releases/v3` action to deploy our build folder to `gh-pages`.

Whenever, you'll push something in your master branch, this workflow will run and will deploy your static build folder to `gh-pages` branch.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/25cf6pqnqo19q0b3f0ic.png)

Now, when the deployment is completed, you'all have your app running at you github link [https://yourusername.github.io/markdown-editor/](https://yourusername.github.io/markdown-editor/).
> Don't forget to add `"homepage" : "https://yourusername.github.io/markdown-editor/"` in `package.json` file, otherwise serving of static contents may cause problem.


If you liked my article, you can follow me on Twitter for my daily paper `The JavaSc®ipt Showcase`, also you can follow my personal projects over Github. Please post in comments, how do you like this article. Thanks!!

{% twitter 1233694229232324608 %}
{% github https://github.com/ashwamegh/react-typescript-markdown-editor no-readme %}