This page looks best with JavaScript enabled

How to Auto Format Links with Decorators in Slate-React

 ·  ☕ 5 min read  ·  ✍️ Iskander Samatov

Auto Format Links with Decorators in Slate-React


In this tutorial, I’ll show you how to auto format links using custom decorators in Slate-react. Surprisingly, I found the process to be not as straightforward as I imagined.

While Slate does provide a link plugin , you need to use a specific button provided by the library to attach a link to your text. Instead, what I wanted was for the editor to automatically detect whenever there’s a link in my text and format it accordingly.

Thankfully, the library provides decorators that allow us to achieve just that. Let’s get started!

Quick overview of Slate-react

Before we start adding decorators, let’s do a quick review of Slate-react. Slate-react is a library that adapts Slate rich-text editors to React.

In Slate, every piece of text is represented as a node, and the library lets us style our nodes by applying custom HTML and CSS.

To learn more about Slate-react, check out the official docs.

Now let’s move on with our step-by-step tutorial.

Setup basic editor

First, let’s just set up a basic editor.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { useState } from "react";
import { withReact, Slate, Editable } from "slate-react";
import { createEditor } from "slate";

const initialValue = [
  {
    type: "paragraph",
    children: [{ text: " " }],
  },
];

export const Editor = () => {
  const [editor] = useState(() => withReact(createEditor()));

  return (
    <div>
      <h2>Your custom editor</h2>
      <Slate editor={editor} value={initialValue}>
        <Editable />
      </Slate>
    </div>
  );
};

Here, we’re simply creating an Editable component that renders our editor. You can find more information on how to set up the Slate editor in this installing Slate guide .

Now that we have our editor in place let’s start adding the decorator.

Adding custom decorator

In Slate, decorators are custom functions that run every time the editor’s content changes. They allow attaching metadata to our nodes which we can use later when rendering them.

The Editable component accepts a prop called decorate. This is where we specify our custom decorator functions.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
const myDecorator = ([node, path]) => {
  const nodeText = node.text;

  if (!nodeText) return [];

  const urls = findUrlsInText(nodeText);

  return urls.map(([url, index]) => {
    return {
      anchor: {
        path,
        offset: index,
      },
      focus: {
        path,
        offset: index + url.length,
      },
      decoration: "link",
    };
  });
};

The decorator function accepts a NodeEntry object, which is a tuple that contains node and path objects. We will use both to find any links in the node’s text and attach their location as metadata to the node.

findUrlsInText is a custom function that uses regex to find the indexes of all of the links in the text:

1
2
3
4
5
6
7
8
9
export const findUrlsInText = (text) => {
  const urlRegex =
    // eslint-disable-next-line no-useless-escape
    /(?:(?:https?|ftp|file):\/\/|www\.|ftp\.)(?:\([-A-Z0-9+&@#\/%=~_|$?!:,.]*\)|[-A-Z0-9+&@#\/%=~_|$?!:,.])*(?:\([-A-Z0-9+&@#\/%=~_|$?!:,.]*\)|[A-Z0-9+&@#\/%=~_|$])/gim;

  const matches = text.match(urlRegex);

  return matches ? matches.map((m) => [m.trim(), text.indexOf(m.trim())]) : [];
};

Once we have our decorator function, we can provide it as a prop to our Editable:

1
<Editable decorate={myDecorator} />

Now every time the content of the editor changes, our decorator function will run. Whenever it finds a link, it will return it’s location as a metadata, which we can use when rendering the node.

You won’t see any visible difference yet since we’re not doing anything with this metadata. Let’s fix that.

Adding custom function to render leaf nodes

A leaf is an HTML component that displays the content of our node. The Editable component accepts a prop called renderLeaf for specifying how to render our leaf nodes.

The renderLeaf function accepts a props object, which contains the metadata of our leaf node. More specifically, we get the { attributes, children, leaf } object, where leaf.decorations is the metadata we attached earlier in our decorator function.

Now let’s write a simple function for rendering leaves for the nodes in our editor. If the leaf contains any links, we want to render them as anchor tags. Otherwise we will render the leaves as span elements.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
const Leaf = ({ attributes, children, leaf }) => {
  if (leaf.decoration === "link") {
    children = (
      <a
        style={{ cursor: "pointer" }}
        href={leaf.text}
        onClick={() => {
          window.open(leaf.text, "_blank", "noopener,noreferrer");
        }}
      >
        {children}
      </a>
    );
  }

  return <span {...attributes}>{children}</span>;
};

You might notice I’m adding both href and onClick for the link. This is a workaround I came up with since links rendered in Slate leaves are not clickable for some reason. So we need to add a manual on click to ensure it opens the URL in the new tab while adding the href attributes gives it the UI treatment of the link via HTML.

The final step is to provide our Leaf function as a prop to our Editable:

1
<Editable renderLeaf={Leaf} decorate={myDecorator} />

And that’s all there’s to it. Our editor should automatically format any links we add to our text! At this point, you can add more bells and whistles to your editor, like adding an icon next to external links, etc.

Conclusion

In this post we learned how to format links with decorators in Slate-react.

Adding a custom decorator allows us to format our links however we want automatically whenever a link text is inserted in the editor.

Here’s the codesandbox with the full example used in this tutorial. I hope you found this article helpful. Thank you for reading!

If you’d like to get more web development, React and TypeScript tips consider following me on Twitter where I share things as I learn them.
Happy coding!

Share on

Software Development Tutorials
WRITTEN BY
Iskander Samatov
The best up-to-date tutorials on React, JavaScript and web development.