📅 3 years ago 🕒 12 min read 🙎♂️ by Madza
Long gone are the days when developers coded in Notepad and blogs displayed the code blocks using just HTML. Highlighted code is so much more pleasing to the eye and way easier to read.
In this tutorial, we will create a React code editor and Syntax highlighter, so you can type in your code and see how it gets highlighted. We will also provide interactivity, meaning users will be able to switch between multiple languages and themes.
The source code will be available here, for reference.
First, let's create a simple wireframe to design the layout of the consisting components.
The whole app will reside in the App
, which will be the main wrapper for our application.
Inside the App
there will be ControlsBox
and PanelsBox
components.
ControlsBox
will further include two Dropdown
components. One will be for selecting the input language, and the other for selecting the theme of the highlighting.
To create a project boilerplate, we will be using Create React app, which will set up a fully configured React project in a minute or less.
To do that, open your terminal and run the following command:
npx create-react-app syntax-highlighter
Then switch to the newly created folder by running cd syntax-highlighter
and start the React development server by running npm start
.
This should automatically open up your browser and you should be presented with a React default app on port 3000.
Open the src
folder and remove all the files except App.js
, App.css
, index.js
, and index.css
. Then remove the content in each of those, as we will re-write each file entirely from scratch.
First, we will create the base structure of our project to build upon.
Let's start with index.js
file, which will render our app. Open it up and include the following code:
import ReactDOM from "react-dom"; import "./index.css"; import App from "./App"; ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode>, document.getElementById("root") );
In order to be able to render, we first imported ReactDOM
component. Then we imported an extended stylesheet to style the base. Finally, we imported the App
component and set it up, so that it will be rendered in the root
element inside the DOM tree.
Then open index.css
file and include the following styles:
* { margin: 0; padding: 0; box-sizing: border-box; } body { width: 100vw; min-height: 100vh; font-family: sans-serif; background-color: #ffdee9; background-image: linear-gradient(0deg, #ffdee9 0%, #b5fffc 100%); }
We first created the reset rules for margin
, padding
, and box-sizing
, so we do not have to worry about the default browser values for these later. It's a common practice and recommended for any project you ever build from scratch.
We also created specific rules for body
, so that it always fills the entire viewport of the screen. We also set a particular font-family and a gradient background.
Then open the App.js
, where all the logic of our app will live. Include the following code:
import "./App.css"; export default function App() { return ( <div className="App"> <div className="ControlsBox"></div> <div className="PanelsBox"></div> </div> ); }
First, we imported an external stylesheet for App.js
.
Then we created an App
function, which we will be rendered in the previously created index.js
. Inside it, we created an App
div element, which will be the main wrapper for our app. Furthermore, inside the App
wrapper there will be ControlsBox
and PanelsBox
components.
Then open App.css
file and add the following styles:
.App { max-width: 1200px; margin: 0 auto; padding: 20px; } .ControlsBox { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; } .PanelsBox { display: grid; grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); gap: 20px; margin-top: 20px; }
First, we made sure that the App
wrapper never exceeds specific width. We also centered it in the viewport and added the padding inside it.
For the ControlsBox
children we set the grid layout with two columns, each of the same width. We also added a gap between both columns.
The PanelsBox
children will also use a grid layout with two columns and a gap between them. The layout will automatically switch to one column if the width of the children is less than 400px
, meaning the included Editor
and Highlighter
components will be shown below each other. To separate PanelsBox
from ControlsBox
we added a margin on the top.
There will be user interaction by selecting the languages and themes, so the data will change. To display them properly on the screen, we will need to store them into the state variables.
For that, we will use React built-in useState hook, which is a standard way of handling this in React ecosystem.
Open the App.js
and add the following code:
import React, { useState } from "react"; import "./App.css"; export default function App() { const [input, setInput] = useState(""); const [language, setLanguage] = useState(""); const [theme, setTheme] = useState(""); return ( <div className="App"> <div className="ControlsBox"></div> <div className="PanelsBox"></div> </div> ); }
First, we imported the React useState
hook and then we included input
, language
, and theme
variables inside the App
function.
The input
will keep track of the input user has written in the Editor
, the language
will track the programming language user has selected, and the theme
will track which highlight theme the user has selected.
To divide the building blocks of the app from the app logic we will create several components that we will later import in the App.js
.
We will create a separate folder in the project's root called components
and create separate JS and CSS files for Dropdown
, Editor
, and Highlighter
components.
You can create the files manually, or you can use the terminal command mkdir components && cd components && touch Dropdown.js Dropdown.css Editor.js Editor.css Highlighter.js Highlighter.css
to save time.
We will use Dropdown
component for both language and theme selection. The only variable that will change will be the data we will pass in.
Open the Dropdown.js
file and add the following code:
import "./Dropdown.css"; export const Dropdown = ({ defaultTheme, onChange, data }) => { return ( <select className="select" defaultValue={defaultTheme} onChange={onChange}> {Object.keys(data) .sort() .map((theme, index) => { return ( <option key={index} value={theme}> {theme} </option> ); })} </select> ); };
We first imported the external stylesheet for Dropdown.js
.
We used the select
element and then looped through the data
prop we will receive from the App
to display the available theme options. We sorted the options in alphabetical order.
We also used the defaultProp
so we can later set up the default theme option shown on the initial launch as well as onChange
prop, so we later have control of what happens when the user selects a particular theme.
Then switch to the DropDown.css
file and add the following styles:
.select { height: "100px"; border: none; border-radius: 5px; padding: 5px 0; background-color: #ffffff; width: 100%; }
For the Select
component we set the specific height, removed the default border, rounded the corners, added the padding inside, set the background to white and made sure it uses all the available space of the parent horizontally.
The Editor
component will be the text area, where the user will enter the code.
Open the Editor.js
file and add the following code:
import "./Editor.css"; export const Editor = ({ placeHolder, onChange, onKeyDown }) => { return ( <textarea className="editor" placeholder={placeHolder} onChange={onChange} ></textarea> ); };
We first imported the external stylesheet for Editor.js
.
Then we returned the textarea
element and included the placeholder
prop that will display the placeholder value on the initial launch. We also included the onChange
prop so we later have control of what happens when the user types in the code.
Let's add some styling to the Editor
component. Open Editor.css
and include the following styles:
.editor { border: none; min-height: 300px; padding: 10px; resize: none; }
For the Editor
component we removed the default border, set the minimum height, and added padding.
We also made sure the editor block is not manually resizable by the user. It will still automatically adjust its height based on the content user has typed in.
In order to highlight the code bocks we will use react-syntax-highlighter package. To install it, run the following command on your terminal:
npm install react-syntax-highlighter
Then open the Highlighter.js
file and include the following code:
import SyntaxHighlighter from "react-syntax-highlighter"; import "./Highlighter.css"; export const Highlighter = ({ language, theme, children }) => { return ( <SyntaxHighlighter language={language} style={theme} className="highlighter" > {children} </SyntaxHighlighter> ); };
We first imported the SyntaxHighlighter
component, then imported an external stylesheet for Highlighter.js
.
The SyntaxHighlighter
required language
and style
. We will pass those in once we import the Highlighter
into App.js
.
Next open Highlighter.css
file and add the folowing style rule:
.highlighter { min-height: 300px; }
This will ensure that the Highlighter
component always uses minimal height, which will be useful if there is no content (to avoid the component from auto-shrinking).
In this phase we will put everything together, making the app functional.
Open the App.js
file and add the following code:
import React, { useState } from "react"; import { Dropdown } from "../components/Dropdown"; import { Editor } from "../components/Editor"; import { Highlighter } from "../components/Highlighter"; import * as themes from "react-syntax-highlighter/dist/esm/styles/hljs"; import * as languages from "react-syntax-highlighter/dist/esm/languages/hljs"; import "./App.css"; const defaultLanguage = `${"javascript" || Object.keys(languages).sort()[0]}`; const defaultTheme = `${"atomOneDark" || Object.keys(themes).sort()[0]}`; export default function App() { const [input, setInput] = useState(""); const [language, setLanguage] = useState(defaultLanguage); const [theme, setTheme] = useState(defaultTheme); return ( <div className="App"> <div className="ControlsBox"> <Dropdown defaultTheme={defaultLanguage} onChange={(e) => setLanguage(e.target.value)} data={languages} /> <Dropdown defaultTheme={defaultTheme} onChange={(e) => setTheme(e.target.value)} data={themes} /> </div> <div className="PanelsBox"> <Editor placeHolder="Type your code here..." onChange={(e) => setInput(e.target.value)} /> <Highlighter language={language} theme={themes[theme]}> {input} </Highlighter> </div> </div> ); }
First, we imported the Dropdown
, Editor
and Highlighter
components as well as all the supported themes and languages from react-syntax-highlighter
.
Then we set the defaultLanguage
variable to javascript
. If it is not available from the languages list we imported, we set the defaultlanguage
to the first language available in the imported languages list. Same for the defaultTheme
. We set defaultTheme
variable to atomOneDark
. If it is not available in the imported themes list, the defaultTheme
value will be set to the first available theme from the imported themes list.
For the Dropdown
components we set the defaultLanguage
and defaultTheme
that will be displayed once the app is first rendered. We also set the onChange
behavior to update the language
and theme
variable states when the user makes selections from dropdowns. Finally, we passed in the data
prop, that will generate the dropdown options list.
For the Editor
component we set the placeHolder
component to ask the user to enter some input once the app is first rendered as well as set onChange
function that updates the input
state variable each time the user write something in the Editor
.
Finally, for the Highlighter
component we passed in the language
variable state, so it knows which language to render and the themes
variable state so it knows how to style it.
The last thing left to do is to test our app. Check your terminal to see if the development server is still running (if it is not, run npm start
) and open the browser.
You should be presented with the functional code editor and highlighter:
In this tutorial, we learned how to create a wireframe for an app, use states, create components, style them and create the app logic.
From now on, every time you need to pick up the most appropriate theme, you don't need to build a test application anymore. You will now have your own tool that you can use.
In the future, you can customize the project further by adding the auth system and database, so the user is able to save their snippets, creating a full-stack playground.
Thanks for reading and I hope you learned a thing or two from this tutorial.