Designing a Markdown note taking application in ReactJS
In this article, we will learn how to design a Markdown note-taking application in React that can help one to quickly jot down notes and download the rendered output. The use of Markdown allows one to spend less time on formatting the text and therefore create rich text content using only the Markdown syntax.
We will be using the React library to quickly create the User Interface as it would handle the updating of the DOM as required. We will use Bootstrap CSS to design the application and make it user-friendly. We will also use a Blob to save the content to our device.
We will be following the below steps for creating our application:
Step 1: Creating the project
We will start by creating a new react project using the create-react-app tool. You can use the following command for creating one with the desired name. Please ensure that Node and npm are installed on the system.
npx create-react-app react-md-note-app
Project Structure: We will then delete all the files that are not necessary. The file structure after deleting the files is given below:
Project Directory
Step 2: Installing necessary modules
We will start by adding the Bootstrap CDN to the index.html page. The CDN links can be found here and they can directly be added in the head section of the index page.
We will also need two npm packages, one is the markdown-it package that does the heavy-lifting of parsing the Markdown content and giving us the rendered output in HTML. We can simply use this HTML for displaying our output. The other module is the file-saver package that simplifies the task of saving the given blob to the system. The following command is used for installing these two packages:
npm install markdown-it file-saver
Step 3: Creating a simple User Interface using Bootstrap
We will create the layout using the Bootstrap Grid system to ensure that our application is mobile responsive and looks good without writing a lot of CSS.
The application can be divided into 3 parts; the markdown editor that will allow the user to enter the notes in Markdown, the output area that shows the rendered HTML, and the file saving options that contain the input field for the filename and the buttons for saving the content. Each of the parts is nested using the Card component of Bootstrap and the relevant components are added to the input elements.
We will also need some CSS rules that can be imported into our App.js file. We can create a new file named App.css and write the rules as below:
App.css:
CSS
body { background-color : yellow; } .markdown-editor, .rendered-output { height : 100% ; width : 100% ; padding : 10px ; } .markdown-textarea, .rendered-html-output { height : 60 vh; width : 100% ; overflow : auto ; } |
Step 4: Creating the hooks that will be used to manage the state
We will create 3 hooks using the useState method of React for managing the state in the 3 parts of our application. Each of the hooks returns the variable itself with its initial state and the function that can be used to set the state of that variable. React will automatically re-render the component when its state changes.
This will allow us to simply use the respective functions and the variables for handling the state of each component.
Step 5: Using the state variables in the application
We will now use these variables inside the 3 components we created earlier.
The markdownText variable is used in the textarea where we will write our markdown text. We will use this in the value property of the textarea. Using the setMarkdownText function we can change the value of the text here and it would be displayed via this value property.
The renderedHTML variable is used in the div element that will show the rendered output of the markdown. As the MarkdownIt module will return the HTML from the markdown, we will need to set the inner HTML of this element directly. This will require the use of the dangerouslySetInnerHTML property of the element and passing an object with the key of _html.
Note: This is a feature in React that discourages the practice of directly modifying the HTML to prevent Cross-Site Scripting (XSS) attacks on a webpage. It is recommended to sanitize any user-input HTML content before directly using it.
Finally, the fileName variable is used in the file saver options so that it can be used to enter the filename of the content to be saved. We will also bind the onChange event handler to setFileName so that any value entered by the user is updated to the variable. We can do this by accessing the event target and getting the value property.
Step 6: Creating the functions we have defined earlier
The handleTextInput() function: This function will take the text that is entered in the markdown editor and use MarkdownIt to render the HTML needed.
We will first get the text that is entered by getting the value of the target. We then initialize a new instance of MarkdownIt and use its render method. We will then use setMarkdownHTML function to set this in the output area where our final HTML will be displayed. This function will be used in the onChange property of the markdown editor.
The saveHTML() and saveMarkdown() functions: These functions will be used to save content to our system with the given filename. In both of these functions, we will create a Blob object with the content to be saved. We can use the renderedHTML and markdownText variables to get the current text content of the application.
The type of the blob is specified next. We can use text/html for the rendered output so that it is saved as an HTML file and only text for the markdown text.
We will now use the saveAs() method of the FileSaver module to save the file to the system. This method uses two parameters, the first one is the blob object that has to be saved and the second one is the file name that should be used to save the file. We can get the file name by using the fileName variable and pass the blob object we created earlier.
We can bind these functions with the respective onClick event handlers of the two buttons we have created. Clicking on the buttons will open a Save As dialog on the browser with the given filename and the user can choose to save the file as required.
App.js:
Javascript
import { useState } from 'react' ; // Import the required modules import MarkdownIt from 'markdown-it' ; import FileSaver from 'file-saver' ; import './App.css' ; function App() { // Create the states that will be used // through the application // This state will be used for the Markdown text let [markdownText, setMarkDownText] = useState( "" ); // This state will be used for the rendered HTML let [renderedHTML, setRenderedHTML] = useState( "" ); // This state will be used for the filename while saving let [fileName, setFileName] = useState( "untitled-note" ); // Create a function that will be invoked // whenever the user modifies the content // in the textarea function handleTextInput(e) { // Change the text of the markdown side setMarkDownText(e.target.value); // Initialize a MarkdownIt instance let md = new MarkdownIt(); // Render out the markdown to HTML using // the render() method let renderedHTML = md.render(e.target.value); // Change the markdown's side to the rendered HTML setRenderedHTML(renderedHTML); } // Create a function download the rendered HTML function saveHTML() { // Create a new Blob of the type 'text/html' // using the rendered HTML content let blobFile = new Blob([renderedHTML], { type: "text/html" }); // Save the file using the given file name FileSaver.saveAs(blobFile, fileName); } // Create a function download the Markdown text function saveMarkdown() { // Create a new Blob of the type 'text' // using the markdown content let blobFile = new Blob([markdownText], { type: "text" }); // Save the file using the given file name FileSaver.saveAs(blobFile, fileName); } return ( <div className= "container" > <h2 className= "app-heading text-center display-4 my-3" > React Markdown Notes </h2> <div className= "row" > <div className= "col col-sm-12 col-md-6" > { /* Card for the markdown editor */ } <div className= "card bg-light markdown-editor" > <h4 className= "card-title text-center" > Markdown </h4> <div className= "card-body" > { /* Textarea for the markdown editor */ } <textarea className= "form-control markdown-textarea" rows={20} value={markdownText} onChange={handleTextInput} ></textarea> </div> </div> </div> <div className= "col col-sm-12 col-md-6" > { /* Card for the markdown editor */ } <div className= "card bg-light rendered-output" > <h4 className= "card-title text-center" > Output </h4> { /* Textarea for the markdown editor */ } <div className= "card-body" > <div // Change the HTML to be displayed according // to the render produced by MarkdownIt dangerouslySetInnerHTML={{ __html: renderedHTML }} className= "rendered-html-output" > </div> </div> </div> </div> </div> <div className= "row" > <div className= "col col-sm-12" > { /* Card for the save files option */ } <div className= "card bg-light my-3" > <div className= "card-body" > <h4>Save Content</h4> <div className= "input-group" > <input type= "text" className= "form-control" placeholder= "File name" aria-label= "File name" value={fileName} onChange={fname => setFileName(fname.target.value)} /> { /* Buttons for saving the text */ } <div className= "input-group-append" > <button className= "btn btn-primary" type= "button" onClick={saveMarkdown}> Save Markdown </button> <button className= "btn btn-primary" type= "button" onClick={saveHTML}> Save HTML </button> </div> </div> </div> </div> </div> </div> </div> ) } export default App; |
Step 6: Running and Building the application
We can run this application by using the following command. This will start React’s development server that can be used for debugging our application.
npm run start
We can also build this application so that it could be hosted on any platform that supports static file hosting. We can build our application using the following command to run the project:
npm run build
Output:
Source Code: https://github.com/sayantanm19/react-markdown-notes-gfg
Please Login to comment...