DEV Community

Cover image for Slate | Editor in 10min with Next.js and TS ✍️
Nils Jacobsen for Zirkular

Posted on

Slate | Editor in 10min with Next.js and TS ✍️

What we're going to do?

Building a holistic rich text editor experience for a good user experience is hard, but recently I found Slate

Slate repo
Link to Repo

Slate is a customizable framework for building rich text editors. The community around Slate looked pretty active, so we Zirkular it a shot. After some quick prototypes I was stunned how easy it is to use Slate out of the box in minutes.

So we use Slate now for 3 month. Now I felt like it's time to give something back to open source. So this should become a blog series with little slate tutorials in addition to the Slate Docs.

I tried to make a very detailed description, so if you follow the steps you should get to the goal in 10 minutes. If you have problems or questions feel free to use the comment section.

You can find the final state of the code in this repo.
zirkular-os-projects

Step 1 - Create Next.js App 🏗

Use the following command in the terminal to create a Next.js app. Reference
yarn create next-app
Go through the process of crest-next-app. After it's finished you have the app in your root. Now your app is ready for development.

Step 2 - Clean up time 🧹

For having a nice clean project it's time to delete the stuff that we don't need.

  • Delete Home.module.css
  • Delete content in public folder
  • Update index.js in pages folder
    • Remove any the imports
    • Remove any jsx elements so its only one div in it
  • Update _app.js in pages folder
    • make sure to import "../styles/globals.css"

Your folder structure should now look like this.
folder

Your updated index.js file should look like this.

export default function Home() {
  return <div>Editor Page</div>
}
Enter fullscreen mode Exit fullscreen mode

Your updated _app.js file should look like this.

import "../styles/globals.css";

function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />
}

export default MyApp;
Enter fullscreen mode Exit fullscreen mode

Your updated styles/gloabals.css file should look like this.

html,
body {
  padding: 0;
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
    Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
}
Enter fullscreen mode Exit fullscreen mode

On your index page you now have the simple headline Editor Page on an empty page.

Step 3 - Make it type-safe 🌟

To use Typescript in our setup we have to make some configurations in the tsconfig.json file. We don't have the file yet, so create it at the root level. The file should contain:

{
  "compilerOptions": {
    "target": "es5",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": true
  },
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
  "exclude": ["node_modules", "cypress"]
}
Enter fullscreen mode Exit fullscreen mode

Step 4 - Create the editor component 🧩

To add components to a Next.js project you have to create a component folder (you can also name that different) on the root level of your app. After that you can add the file myEditor.tsx to this folder.

In this component we write a simple function component. With a simple text like My Editor.

const MyEditor = () => {
  return <div>My Editor</div>
}

export default MyEditor;
Enter fullscreen mode Exit fullscreen mode

Now you can call the component in your index.js file.

import MyEditor from "./../components/myEditor"

export default function Home() {
  return (
    <div>Editor Page
      <MyEditor />
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

Because at some point in time we want to style this component we use the module.css scoped styling strategy. Therefore we add the Editor.module.css file in the styles folder.

We are not going to use some styling but for the next blog we need this to be in place.

Import the styling in the component so we can now use 'styles' now in the component.

import styles from "../styles/Editor.module.css";

const MyEditor = () => {
  return <div>My Editor</div>
}

export default MyEditor;
Enter fullscreen mode Exit fullscreen mode

So if we have done everything right we see now that we have the Editor Page text from the index.js component and the My Editor text from the component/myEditor.tsx.

Step 5 - Adding Slate 🚀

Use the following commands to add Slate with you terminal.
yarn add slate slate-react
yarn add react react-dom

The next steps are probably a duplication of the installing Slate tutorial at the docs but I will still cover this section, because I felt kind of confused sometimes because it's switching between js and ts.

Once you've installed Slate, you'll need to import it and also some types for type-safety.

import { useState } from "react";

// Import the Slate editor factory.
import { createEditor } from 'slate';

// Import the Slate components and React plugin.
import { Slate, Editable, withReact } from 'slate-react';

//For TypeScript
import { BaseEditor } from 'slate';
import { ReactEditor } from 'slate-react';


declare module 'slate' {
  interface CustomTypes {
    Editor: BaseEditor & ReactEditor
    Element: CustomElement
    Text: CustomText
  }
}

type CustomElement = { type: 'paragraph'; children: CustomText[] }
type CustomText = { text: string }
Enter fullscreen mode Exit fullscreen mode

The next step is to create a new Editor object in our component.

const MyEditor = () => {
  const [editor] = useState(() => withReact(createEditor()))
  return <div>My Editor</div>
}
Enter fullscreen mode Exit fullscreen mode

Now create the editor's initialValue outside of the function. Slate processes an object in the background that contains all the nodes and inline elements of our editor.

const initialValue: CustomElement[] = [
  {
    type: 'paragraph',
    children: [{ text: 'A line of text in a paragraph.' }],
  },
]
Enter fullscreen mode Exit fullscreen mode

We can use this initialValue to set this object, but remember that we can only use this value prop to set an initial state. It is not intended to change the value of the editor like in a controlled input field. So add the Slate component and pass in the editor and value prop. You can think of the Slate component as providing a context to every component underneath it.

const MyEditor = () => {
  const [editor] = useState(() => withReact(createEditor()))
  return <Slate editor={editor} value={initialValue}>
    <Editable />
  </Slate>
}
Enter fullscreen mode Exit fullscreen mode

In the end the myEditor.tsx file should look like this.

import styles from "../styles/Editor.module.css";
import { useState } from "react";

// Import the Slate editor factory.
import { createEditor } from 'slate';

// Import the Slate components and React plugin.
import { Slate, Editable, withReact } from 'slate-react';

//For TypeScript
import { BaseEditor } from 'slate';
import { ReactEditor } from 'slate-react';

declare module 'slate' {
  interface CustomTypes {
    Editor: BaseEditor & ReactEditor
    Element: CustomElement
    Text: CustomText
  }
}

type CustomElement = { type: 'paragraph'; children: CustomText[] }
type CustomText = { text: string }


const initialValue: CustomElement[] = [
  {
    type: 'paragraph',
    children: [{ text: 'A line of text in a paragraph.' }],
  },
]

const MyEditor = () => {
  const [editor] = useState(() => withReact(createEditor()))
  return <Slate editor={editor} value={initialValue}>
    <Editable />
  </Slate>
}

export default MyEditor;
Enter fullscreen mode Exit fullscreen mode

Step 6 - Cheering 🎉

Look at the result. Now you can edit the text. It's only plain text, but a good foundation to start expanding the functionality.

In the next part we are going to create the first element types.

If you want to see how a finished version could look like, check out our community-first platform for open source projects: Zirkular

Zirkular

Top comments (0)