Building a code editor within NextJS
Published: 2022-01-15 04:56:30

---
title: Building a code editor within NextJS
published: true
description: 
tags: 
cover_image: https://ayseimg.s3.amazonaws.com/codeEditor.png
---

I've found examples of building a code editor within React, but the npm packages that are used throw a window / navigator not found in NextJS. This is how I got around that.

##Create App

First create your app
`npx create-next-app@latest`
 then I am going to [install Tailwind] (https://tailwindcss.com/docs/guides/nextjs)
run `npm run dev` and bam! Let's set up our folder structure. Create a components folder in the root directory and add 4 files: `editor.js`, `editorApp.js`, `editor.module.css`, and `useLocalStorage.js`.
Sweet, we are ready to build this editor.

##editor.module.css
```
@media (min-width: 500px) {
    .topPane {
        display: flex;
    }
}
```

##Skeleton
Let's start by adding to our `editor.js` (you'll need to install the classnames module)
```
const Editor = () => {
    return(
        <div>Editor</div>
    )
}

export default Editor;
```
Then we'll import that into our `editorApp.js` (3 times for html, css, and js)
```
import Editor from "./editor";
import * as style from "./editor.module.css";
import cn from "classnames";

const EditorApp = () => {
    return (
        <>
        <div className={cn("bg-zinc-500", style.topPane)}>
             <Editor />
             <Editor />
             <Editor />
         </div>
            <div style={{ height: "68vh" }}>
                <iframe
                title="output"
                sandbox="allow-scripts"
                frameBorder="0"
                height="100%"
                width="100%" />
            </div>
        </>
    )
};

export default EditorApp;
```
And then we can rip out all the boiler code in `index.js` and import our editorApp
```
import CodeEditor from "../components/editorApp";

export default function Home() {
  return (
    <CodeEditor />
  )
}
```
You should see Editor 3 times on your screen. Cool.

##Editor.js

We need to install [codemirror](https://www.npmjs.com/package/react-codemirror2-react-17), so run `npm install react-codemirror2-react-17 codemirror --save`
Let's import the styles for codeMirror and our theme. You can test different themes [here](https://codemirror.net/demo/theme.html), and we will also import the codemirror editor.
```
import "codemirror/lib/codemirror.css";
import "codemirror/theme/tomorrow-night-bright.css";
import { Controlled as ControlledEditor } from "react-codemirror2-react-17";
```
We have to import the languages differently as to not throw a ReferenceError of navigator is not defined.
```
let languageLoaded = false;
if (typeof window !== "undefined" && typeof window.navigator !== "undefined") {
    require("codemirror/mode/xml/xml");
    require("codemirror/mode/css/css");
    require("codemirror/mode/javascript/javascript");
    languageLoaded = true;
}
```
We are going to add create our editor now, with a title and body. This includes our props and `handleChange` function.
```
import "codemirror/lib/codemirror.css";
import "codemirror/theme/tomorrow-night-bright.css";
import { Controlled as ControlledEditor } from "react-codemirror2-react-17";

let languageLoaded = false;
if (typeof window !== "undefined" && typeof window.navigator !== "undefined") {
    require("codemirror/mode/xml/xml");
    require("codemirror/mode/css/css");
    require("codemirror/mode/javascript/javascript");
    languageLoaded = true;
}

const Editor = (props) => {
    const { language, editorTitle, value, onChange, className } = props;

    function handleChange(editor, data, value) {
        onChange(value);
    }

    return(
        <div className={className}>
            <div className="flex justify-between text-white rounded-t-lg py-2 pr-2 pl-4 bg-zinc-700">
                {editorTitle}
            </div>
            <ControlledEditor
                onBeforeChange={handleChange}
                value={value}
                className="grow rounded-b-lg overflow-hidden text-left"
                options={{
                    lineWrapping: true,
                    lint: true,
                    mode: language,
                    theme: "tomorrow-night-bright",
                    lineNumbers: true,
                }}
            />
        </div>
    )
}

export default Editor;
```

##editorApp.js

We need to fill out our `<Editor />` now. Add a 
`const [html, setHtml] = useState('');` (import useState)
and for the first editor
```
  <Editor
   language="xml"
   editorTitle="HTML"
   value={html}
   onChange={setHtml}
   />
```
Copy this pattern for css and javascript.
Then we are going to add a useEffect to set the iframe srcDoc.
```
const [srcDoc, setSrcDoc] = useState("");

useEffect(() => {
    const timeout = setTimeout(() => {
      setSrcDoc(`
        <html lang="en">
          <body>${html}</body>
          <style>${css}</style>
          <script>${js}</script>
        </html>
      `);
    }, 200);

    return () => clearTimeout(timeout);
  }, [html, css, js]);
```
and add the srcDoc prop as srcDoc to the iframe. You should have
```
import { useState, useEffect } from "react";
import Editor from "./editor";
import * as style from "./editor.module.css";
import cn from "classnames";

const EditorApp = () => {
  const [html, setHtml] = useState("");
  const [css, setCss] = useState("");
  const [js, setJs] = useState("");
  const [srcDoc, setSrcDoc] = useState("");

  useEffect(() => {
    const timeout = setTimeout(() => {
      setSrcDoc(`
        <html lang="en">
          <body>${html}</body>
          <style>${css}</style>
          <script>${js}</script>
        </html>
      `);
    }, 200);

    return () => clearTimeout(timeout);
  }, [html, css, js]);

  return (
    <>
      <div className={cn("bg-zinc-500", style.topPane)}>
        <Editor
          className="px-2 py-3 md:w-1/3 md:pl-3 md:pr-2"
          language="xml"
          editorTitle="HTML"
          value={html}
          onChange={setHtml}
        />
        <Editor
          className="px-2 py-3 md:w-1/3 md:px-2"
          language="css"
          editorTitle="CSS"
          value={css}
          onChange={setCss}
        />
        <Editor
          className="px-2 py-3 md:w-1/3 md:pl-1 md:pr-3"
          language="javascript"
          editorTitle="JS"
          value={js}
          onChange={setJs}
        />
      </div>
      <div style={{ height: "68vh" }}>
        <iframe
          srcDoc={srcDoc}
          title="output"
          sandbox="allow-scripts"
          frameBorder="0"
          height="100%"
          width="100%"
        />
      </div>
    </>
  );
};

export default EditorApp;

```
You should now be able to type in the the editors and see your results displayed.

##useLocalStorage.js
Last step.
In this component, we use `localStorage`, which will either throw a `localStorage is not defined` or `window is not defined` in NextJS.[NextJS pre-renders](https://nextjs.org/docs/basic-features/pages) every page which results in Static Site Generation or Server Side Rendering. `localStorage` is a property of the window object which is available on the client side / browser.
To get around this, we first have to check if the window is defined.

```
import { useEffect, useState } from "react";

function getStorageValue(key, initialValue) {
  if (typeof window !== "undefined") {
    const savedValue = localStorage.getItem(key);
    return savedValue !== null ? JSON.parse(savedValue) : initialValue;
  }
}

export const useLocalStorage = (key, initialValue) => {
  const [value, setValue] = useState(() => {
    return getStorageValue(key, initialValue);
  });

  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);

  return [value, setValue];
};
``` 
Import and replace useState in `editorApp.js` for your html, css, and js with useLocalStorage
```
  const [html, setHtml] = useLocalStorage("html", "");
  const [css, setCss] = useLocalStorage("css", "");
  const [js, setJs] = useLocalStorage("js", "");
```

##Boom
You should now have a working codeEditor. If you want to prefill any section with code, just put your code in the empty "" ex. `const [html, setHtml] = useLocalStorage("html", "<div>foo</div>");`. Keep in mind that localStorage is being saved so you may need to clear your browsing data one to many times.
{% github ayseboogie/next-code-editor %}

This is my first post, I hope it was easy to follow and helps someone out :-)