Editor.js is an amazing free block-style editor. This editor works a lot like the Notion editor with each line treated as a block.

It outputs clean data in JSON-format instead of heavy HTML markup.

In this post, I will explain how you can set up Editor.js with Next.js 13 and use it as a text editor in your application.

In case you are new to Next.js, do check out my post on introduction to Next.js.

1 – Setting up a Next.js 13 Project

The best way to set-up a new project using Next.js 13 is by using create-next-app.

Here’s the command you can use:

$ npx create-next-app@latest

During installation, you’ll see the below prompts and you can choose the default options.

What is your project named? my-app
Would you like to use TypeScript? No / Yes
Would you like to use ESLint? No / Yes
Would you like to use Tailwind CSS? No / Yes
Would you like to use `src/` directory? No / Yes
Would you like to use App Router? (recommended) No / Yes
Would you like to customize the default import alias? No / Yes
What import alias would you like configured? @/*

In the case of this example, I will be showing the JS version so I’ve selected ‘No’ for TypeScript. Also, we are using TailwindCSS for some basic styling.

After the prompts, create-next-app will create a folder with your project name and install the required dependencies within the project.

2 – Installing Editor.js within the Next.js project

The next step is to set-up Editor.js for use within our Next.js project.

Now, Editor.js is structured in an interesting way. There is a core Editor.js library that you need to install with the below command.

$ npm install @editorjs/editorjs

However, each type of block supported by Editor.js is a separate plugin in itself. By default, only the Paragraph block is present in the core package.

For other types of blocks such as Headings, Code, Lists, Image and so on, you’ve to install separate packages.

Here’s a list of all the packages I’ve installed using npm install.

"@editorjs/checklist": "^1.5.0",
"@editorjs/code": "^2.8.0",
"@editorjs/delimiter": "^1.3.0",
"@editorjs/editorjs": "^2.28.0",
"@editorjs/embed": "^2.5.3",
"@editorjs/header": "^2.7.0",
"@editorjs/image": "^2.8.1",
"@editorjs/inline-code": "^1.4.0",
"@editorjs/link": "^2.5.0",
"@editorjs/list": "^1.8.0",
"@editorjs/paragraph": "^2.10.0",
"@editorjs/quote": "^2.5.0",
"@editorjs/simple-image": "^1.5.1",

You can choose the block types depending on your requirements and install only those. Also, you can add more blocks in the future if needed. You can find the complete list of available blocks on this link.

Lastly, you also need to install one more package known as editorjs-html. Basically, this package transforms the JSON-formatted block data to HTML for rendering purposes.

3 – Creating the Editor Component

Within our project’s src directory, we will create a folder named components.

Inside that folder, we will create a file named Editor.js with the below code.

import React, { useEffect, useRef } from "react";
import EditorJS, { OutputData } from "@editorjs/editorjs";
import { EDITOR_TOOLS } from "./EditorTools";

export default function Editor({ data, onChange, holder }) {
  //add a reference to editor
  const ref = useRef();

  //initialize editorjs
  useEffect(() => {
    //initialize editor if we don't have a reference
    if (!ref.current) {
      const editor = new EditorJS({
        holder: holder,
        tools: EDITOR_TOOLS,
        data,
        async onChange(api, event) {
          const data = await api.saver.save();
          onChange(data);
        },
      });
      ref.current = editor;
    }

    //add a return function handle cleanup
    return () => {
      if (ref.current && ref.current.destroy) {
        ref.current.destroy();
      }
    };
  }, []);


  return <div id={holder} className="prose max-w-full" />;
};

Basically, here we are initializing a new instance of EditorJS.

The EditorJS instance takes as input the details of the various plugins we want to enable. This is provided as part of the tools configuration object.

As you can see, we created a separate file named EditorTools to maintain the data of the tools or plugins we want to add to the editor.

See below:

import CheckList from "@editorjs/checklist";
import Code from "@editorjs/code";
import Delimiter from "@editorjs/delimiter";
import Embed from "@editorjs/embed";
import Image from "@editorjs/image";
import InlineCode from "@editorjs/inline-code";
import Link from "@editorjs/link";
import List from "@editorjs/list";
import Quote from "@editorjs/quote";
import SimpleImage from "@editorjs/simple-image";
import Paragraph from "@editorjs/paragraph";
import Header from "@editorjs/header"

export const EDITOR_TOOLS = {
  code: Code,
  header: {
      class: Header,
      config: {
        placeholder: 'Enter a Header',
        levels: [2, 3, 4],
        defaultLevel: 2
    }
  },
  paragraph: Paragraph,
  checklist: CheckList,
  embed: Embed,
  image: Image,
  inlineCode: InlineCode,
  link: Link,
  list: List,
  quote: Quote,
  simpleImage: SimpleImage,
  delimiter: Delimiter
};

This contains a list of all the Block types that we want to enable for our Editor. Depending on your requirement, you can add or remove a particular Block type.

4 – Using the Editor Component in Next.js Component

In Next.js 13, we now have the App router. Each project has an app folder under the src directory.

Within the app folder, we will create a new folder named editor. Inside it, create a file page.js and place the below code within it:

"use client"

import dynamic from "next/dynamic";
import { useState } from "react";

import PreviewRenderer from "@/components/PreviewRenderer";
// important that we use dynamic loading here
// editorjs should only be rendered on the client side.
const Editor = dynamic(() => import("../../components/Editor"), {
    ssr: false,
});

export default function EditorPage() {
    //state to hold output data. we'll use this for rendering later
    const [data, setData] = useState();
    return (
        <div className="grid grid-cols-2 gap-2 m-2">
            <div className="col-span-1">
                <h1>Editor</h1>
                <div className="border rounded-md">
                    <Editor
                        data={data}
                        onChange={setData}
                        holder="editorjs-container"
                    />
                </div>
            </div>
            <div className="col-span-1">
                <h1>Preview</h1>
                <div className="border rounded-md">
                    <div className="p-16">{data && <PreviewRenderer data={data} />}</div>
                </div>
            </div>
        </div>
    );
};

What are we doing over here?

Basically, this is the page where we will be showing the editor. Since, we have placed it inside the editor folder it will be available on http://localhost:3000/editor route.

By default, all the pages in a Next.js app are rendered on the server. Therefore, we cannot use things like useState within them. However, in this case we want to do so to handle the editor state and there the first line “use client” tells Next.js that this component will be rendered on the client.

Without this statement, you’ll get the below error when trying to access the page.

You're importing a component that needs useState. It only works in a Client Component but none of its parents are marked with "use client", so they're Server Components by default.
Learn more: https://nextjs.org/docs/getting-started/react-essentials

Also, since we want to load the EditorJS instance on the client side, we use the dynamic loading for this component using next/dynamic.

See the below code:

const Editor = dynamic(() => import("../../components/Editor"), {
    ssr: false,
});

For testing purpose, we have also create a component that will preview what we type in the Block editor. This is the PreviewRenderer component.

Here’s the code for the same:

const editorJsHtml = require("editorjs-html");
const EditorJsToHtml = editorJsHtml();

export default function PreviewRenderer ({ data }) {
  const html = EditorJsToHtml.parse(data)
  return (
    <div className="prose max-w-full" key={data.time}>
      {html.map((item, index) => {
        if (typeof item === "string") {
          return (
            <div dangerouslySetInnerHTML={{ __html: item }} key={index}></div>
          );
        }
        return item;
      })}
    </div>
  );
};

Basically, here we are using the editorjs-html package to convert the Block data to HTML markup.

5 – Testing our Editor

We can now test our editor.

Start the application dev server by running npm run dev and visit http://localhost:3000/editor.

You can test by entering some text in the Editor part and see it rendered in HTML on the Preview side.

editor.js with next.js 13

Conclusion

As you can see Editor.js brings the power Block-type editing. We can use it to build various types of applications.

In this post, we saw how you can get started with Editor.js in Next.js 13 in a step-by-step manner.

If you have any comments or queries, please mention them in the comments section below.

Categories: NextJS

0 Comments

Leave a Reply

Avatar placeholder

Your email address will not be published. Required fields are marked *