Slate | Editor in 10min with Next.js and TS ✍️
Nils Jacobsen
Posted on October 17, 2022
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 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"
- make sure to import
Your folder structure should now look like this.
Your updated index.js
file should look like this.
export default function Home() {
return <div>Editor Page</div>
}
Your updated _app.js
file should look like this.
import "../styles/globals.css";
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
}
export default MyApp;
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;
}
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"]
}
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;
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>
)
}
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;
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 }
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>
}
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.' }],
},
]
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>
}
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;
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
Posted on October 17, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.