React component and utilities to transform Slate nodes to React.
To install, run one of the following commands:
pnpm:
pnpm add slate-to-react slate slate-reactnpm:
npm i slate-to-react slate slate-reactyarn:
yarn add slate-to-react slate slate-react- You can render Slate nodes using
SlateViewcomponent:
"use client"
// You can use `slate-to-react` with a framework like Next.js (with its App Router) the above directive might be a way to mark "client component".
// Yuo *must* render SlateView within the client components, because it relies on `useMemo` hook.
import {createRoot} from "react-dom/client"
import type {FC} from "react"
import {SlateView} from "slate-to-react"
const App: FC = () => (
<SlateView
nodes={[
type: "p",
children: [{
text: "Hello, world!"
}]
]}
/>
)
const root = document.querySelector("#root")
createRoot(root).render(<App />)IMPORTANT: Note that by default slate-to-react will generate a unique id for each node using nanoid to use it as key property of each rendered React component, which is not recommended as the key property must remain consistent between renders.
You can opt-out by enabling strict mode in SlateView, or useSlateToReact, or transformNodes options.
When enabled, NodeNoIdFieldError will be thrown if any node without the id field is encountered.
- You can use
slate-to-reactwith React Server Components too. For that usetransformNodesfunction directly:
import type {FC} from "react"
import type {Node} from "slate-to-react"
import {transformNodes} from "slate-to-react"
interface Props {
nodes: Node[]
}
// This could be a React Server Component
// The `transformNodes` function returns a signle `ReactElement` node, so it's a valid result for Function Component.
// Or you can put `transformNodes` call in to the other's component `children` property.
const MySlateView: FC<Props> = ({nodes}) => transformNodes(nodes)- You can also transform Slate nodes via
useSlateToReacthook used insideSlateViewcomponent:
import {createRoot} from "react-dom/client"
import type {FC} from "react"
import {useSlateToReact} from "slate-to-react"
const App: FC = () => {
// This hook returns a signle ReactElement, so you can even return it from your component, no need for `React.Fragment` or any wrapper element.
const view = useSlateToReact([
id: "1",
type: "p",
children: [{
id: "2",
text: "Hello, world!"
}]
])
return (
<div>
<div>
Transformed Slate nodes:
</div>
<div>
{view}
</div>
</div>
)
}
const root = document.querySelector("#root")
createRoot(root).render(<App />)- You can use
transformNodesfunction directly in your client components as well:
"use client"
import {createRoot} from "react-dom/client"
import type {FC} from "react"
import {useMemo} from "react"
import type {Node} from "slate-to-react"
import {transformNodes} from "slate-to-react"
interface MySlateViewProps {
nodes: Node[]
}
const MySlateView: FC<MySlateViewProps> = ({nodes}) => {
const view = useMemo(() => transformNodes(nodes), [nodes])
return (
<div>
<h1>Post title</h1>
<div>
{view}
</div>
</div>
)
}
const App: FC = () => (
<MySlateView
nodes={[
id: "1",
type: "p",
children: [{
id: "2",
text: "Hello, world!"
}]
]}
/>
)
const root = document.querySelector("#root")
createRoot(root).render(<App />)- You can define and use custom transforms to control the output for each node. For this example, let's define Link transformer. It will render
next/linkcomponent for website-own links and<a>tag for links to external resources:
"use client"
import {
SlateView,
createElementNodeMatcher,
createElementTransform
} from "slate-to-react"
import type {Node, Replace} from "slate-to-react"
import type {Text} from "slate"
import type {FC} from "react"
import NextLink from "next/Link"
import {isInternalUrl} from "./utils/isInternalUrl.js"
type Link = Replace<Node<"a">, {
url: string
children: Text[]
}>
// First of all, we need a matcher for `Link` element node.
// Node that slate-to-react has a bunch of builtin matchers, including `isLink`, so you can skip this step
export const isLink = createElementNodeMatcher<Link>(
(node): node is Link => (
node.type === "a" && typeof node.url === "string"
)
)
// Then define a transform for this element. Transform factory function takes two arguments:
// 1. Node matcher. In this case that would be our `isLink` marcher, which implements `ElementMatcher` type.
// 2. Transformer implementation. This function takes `ElementProps` as an argument, and should return `ReactElement` for this node.
export const Anchor = createElementTransform(
isLink,
({key, element, attributes, children}) => (
isInternalUrl(element.url)
? (
<NextLink {...attributes} href={element.url} key={key}>
{children}
</NextLink>
)
: (
<a {...attributes} href={element.url} rel="noopener noreferrer" target="_blank" key={key}>
{children}
</a>
)
)
)
export const MyComponent: FC = () => (
<SlateView
transforms={{
elements: [Anchor] // With that, `SlateView` component will render `Anchor` nodes using our own transform, instead of default.
}}
nodes={[
{
id: "1",
type: "p",
children: [{ // This node will be rendered as regular `<a>` tag because its url points to an external resource
id: "2",
type: "a",
url: "https://example.com",
children: [{
id: "3",
text: "External link to example.com"
}]
}]
},
{
id: "4",
type: "p",
children: [{ // This node will be rendered using `next/link` component, because it has an internal url
id: "5",
type: "a",
url: "/about",
children: [{
id: "6",
text: "About page"
}]
}]
}
]}
/>
)React component that will render given nodes as React elements.
Available props listed in SlateViewProps interface section.
React hook that transforms given Slate nodes to React elements and memoizes the result.
This hook takes following arguments:
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| nodes | Node[] |
Yes | — | List of Slate nodes to transform |
| options | TransformNodesOptions |
No | — | Additional transform options |
Transforms given Slate nodes to react elements.
This function takes following arguments:
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| nodes | Node[] |
Yes | — | List of Slate nodes to transform |
| options | TransformNodesOptions |
No | — | Additional transform options |
Returns ReactElement. All nodes will be wrapped within React.Fragment, so you can even return them from your components as-is.
Takes matcher implementation as the first argument and applies proper types to given function. This will only be useful for TypeScript users. If you use JavaScript - you can write matcher without it, because matchers are just regular functions that returns boolean.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| matcher | LeafNodeMatcher<TLeaf> |
Yes | — | A LeafNodeMatcher implementation function |
Returns LeafNodeMatcher<TLeaf> matcher, where TLeaf type parameter defaults to Slate's Text node. Because crateLeafNodeMatcher is a generic function, it will use actual TLeaf type depending on what you give as type parameter.
Now let's create a RichText matcher, just for a quick demonstration:
import type {Text} from "slate"
import {createLeafNodeMatcher} from "slate-to-react"
export interface RichText extends Text {
bold?: boolean
italic?: boolean
underline?: boolean
strikethrough?: boolean
superscript?: boolean
subscript?: boolean
code?: boolean
}
export const isRichText = createLeafNodeMatcher<RichText>(
(node): node is RichText => (
typeof node.text === "string" && !!(
typeof node.bold === "boolean"
|| typeof node.italic === "boolean"
|| typeof node.strikethrough === "boolean"
|| typeof node.underline === "boolean"
|| typeof node.code === "boolean"
|| typeof node.superscript === "boolean"
|| typeof node.subscript === "boolean"
)
)
)This isRichText matcher will match only Text nodes that have at least one of text formatting property from RichText interface.
Note how we call createLeafMatcher with explicit RichText type parameter. That way the implementation will get proper types for node argument.
It is also important to mention that matcher implementation must return node is LeafProps<TLeaf> and not just boolean. In our case TLeaf will be RichText.
This is similar to createLeafNodeMarcher, but applies types for ElementNodeMatcher to given implementation.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| matcher | ElementNodeMatcher<TElement> |
Yes | — | An ElementNodeMatcher implementation function |
Returns ElementNodeMatcher<TElement> matcher, where TElement defaults to Node<string> which inherits Slate's Element type.
Let's now make a simple Link matcher as the example:
import {createElementNodeMatcher} from "slate-to-react"
import type {Node, Replace} from "slate-to-react"
import {Text} from "slate"
export type Link = Replace<Node, {
children: Text[]
}>
export const isLink = createElementNodeMatcher<Link>(
(node): node is Link => (
node.type === "a" && typeof node.url === "string"
)
)Creates a leaf node transform. It takes LeafNodeMatcher as the first argument to match any specific node during transformNodes call, and transform implementation as the second argument. This transform implementation then will be called for each matched node to create a ReactElement for this node.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| matcher | LeafNodeMatcher<TLeaf> |
Yes | — | A LeafNodeMatcher implementation function |
| transform | TransformImplementation<TLeaf> |
Yes | — | Transform implementation to render matched node with |
Returns LeafTransform<TLeaf>, where TLeaf type parameter defaults to Slate's Text node. The actual value will be infered from TLeafMatcher type, so that transform implementation will get proper types for it props argument.
For this example, let's implement a transform for RichText node.
import {createLeafTransform} from "slate-to-react"
// These were implemented at one of examples above.
import type {RichText} from "./matcher/isRichText.js"
import {isRichText} from "./matcher/isRichText.js"
export const RichText = createLeafTransform(
isRichText,
({key, attributes, leaf, children}) => {
// Render <br /> for empty text blocks as it's probably just an empty line
if (!children) {
return <br {...attributes} />
}
let element: ReactNode = children
if (leaf.bold) {
element = <strong>{element}</strong>
}
if (leaf.italic) {
element = <i>{element}</i>
}
if (leaf.underline) {
element = <u>{element}</u>
}
if (leaf.strikethrough) {
element = <s>{element}</s>
}
if (leaf.superscript) {
element = <sup>{element}</sup>
} else if (leaf.subscript) {
element = <sub>{element}</sub>
}
if (leaf.code) {
element = <code>{element}</code>
}
return <span {...attributes} key={key}>{element}</span>
}
)Creates an element node transform. It takes ElementNodeMatcher as the first argument to match any specific node during transformNodes call, and transform implementation as the second argument. This transform implementation then will be called for each matched node to create a ReactElement for this node.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| matcher | ElementNodeMatcher<TElement> |
Yes | — | An ElementNodeMatcher implementation function |
| transform | TransformImplementation<TElement> |
Yes | — | Transform implementation to render matched node with |
Returns ElementTransform<TElement>, where TElement defaults to Node<string> which inherits Slate's Element type.
Following example implements a transform for Link type:
import {createElementTransform} from "slate-to-react"
// These were implemented at one of examples above.
import {isLink} from "./matcher/isLink.js"
export const Link = createElementTransform(
isLink,
({key, attributes, element, children}) => (
<a {...attributes} href={element.url} key={key}>
{children}
</a>
)
)Matches Text nodes, with or without formatting.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| node | Text |
Yes | — | A Slate Leaf node to test |
Returns true when given node is a Text node.
Matches Paragraph nodes.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| node | Paragraph |
Yes | — | A Slate Element node to test |
Returns true when given node is a Paragraph node.
Matches Link nodes.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| node | Link |
Yes | — | A Slate Element node to test |
Returns true when given node is a Link node.
Matches Blockqoute nodes.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| node | Blockquote |
Yes | — | A Slate Element node to test |
Returns true when given node is a Blockqoute node.
Matches Heading nodes of every valid level (H1-H6).
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| node | Heading |
Yes | — | A Slate Element node to test |
Returns true when given node is a Heading node.
Matches H1 heading nodes.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| node | Heading<"h1"> |
Yes | — | A Slate Element node to test |
Returns true when given node is a Heading<"h1"> node.
Matches H2 heading nodes.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| node | Heading<"h2"> |
Yes | — | A Slate Element node to test |
Returns true when given node is a Heading<"h2"> node.
Matches H3 heading nodes.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| node | Heading<<"h3"> |
Yes | — | A Slate Element node to test |
Returns true when given node is a Heading<"h3"> node.
Matches H4 heading nodes.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| node | Heading<"h4"> |
Yes | — | A Slate Element node to test |
Returns true when given node is a Heading<"h4"> node.
Matches H5 heading nodes.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| node | Heading<"h5"> |
Yes | — | A Slate Element node to test |
Returns true when given node is a Heading<"h5"> node.
Matches H6 heading nodes.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| node | Heading<"h6"> |
Yes | — | A Slate Element node to test |
Returns true when given node is a Heading<"h6"> node.
Stricten extension on top of Slate's Element type. It replaces its children with a self-reference list of Node and adds type property which takes the type of T parameter.
| Name | Extends | Required | Default | Description |
|---|---|---|---|---|
| T | string |
No | string |
A type parameter for Node type property |
Replaces object properties in the L (target) object with the ones from the R (source)
| Name | Extends | Required | Default | Description |
|---|---|---|---|---|
| L | object |
Yes | — | Target object which properties are to be replaced using Source object |
| R | object |
Yes | — | Source object which properties will replace and extend the ones on Target object |
Custom transform lists.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| leaves | LeafTransform[] |
No | [] |
A list of transforms for leaf nodes |
| elements | ElementTransform[] |
No | [] |
A list of transforms for element nodes |
Additional transform options.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| defaultTransforms | boolean |
No | true |
Controls whether default transforms enabled or not |
| transforms | Transforms |
No | undefined |
Custom transforms for Slate nodes |
| strict | boolean |
No | false |
Enables strict mode |
| idKeyName | string |
No | "id" |
The name of the id property on nodes |
| forceGenerateId | boolean |
No | false |
If true, the id for key attribute will be always generated |
| idGenerator | () => string |
No | nanoid |
Custom implementation for ID generator |
Available props for SlateView component. Inherits TransformNodesOptions.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| nodes | Node[] |
Yes | — | List of Slate nodes to transform |
By default slate-to-react has default transforms for following nodes:
- PlainText - transforms only text nodes without formatting into
<span>HTML tag; - RichText - transfomrs only text nodes with at least one of the formatting property into corresponding formatting HTML tag (e. g.
<strong>for bold,<i>for italic etc.) wrapped with<span>HTML tag; - EmptyText - transforms only empty
Textnodes into<br>HTML tag; - Paragraph - transforms
Paragraphnodes into<p>HTML tag; - Link - transforms
Linknodes into<a>HTML tag; - Blockqoute - transforms
Blockqoutenodes into<blockqoute>HTML tag; - Heading - transforms
Headingnodes intoh<level>HTML tag with correspondinglevel(e. g.<h1>,<h2>,<h3>,<h4>,<h5>,<h6>).