slate: Property 'type' does not exist on type 'Node' when trying to apply custom formatting

Description From the example (which isn’t in TS, but I am doing my best to translate as I follow along), using match seemed to work with custom types. This is my code for toggling Custom Formatting:

const [match] = Editor.nodes(editor, {
  match: (n: Node) => {
    return n.type === "code";
  },
});

However, I get the warning:

Property 'type' does not exist on type 'Node'.
  Property 'type' does not exist on type 'CustomText'

Recording Screen Shot 2022-03-25 at 3 25 26 AM

Environment

  • Slate Version:
"slate": "^0.76.0",
"slate-history": "^0.66.0",
"slate-react": "^0.76.0",
  • Operating System: macOS

  • TypeScript Version: "typescript": "^4.6.2"

Context I’ll add the relevant code below

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

type CustomText = { text: string };
type CustomElement =
  | { type: "paragraph"; children: CustomText[] }
  | { type: "code"; children: CustomText[] };

const RichEditor = (): ReactElement => {
  const editorRef = useRef<Editor>();

  if (!editorRef.current) {
    editorRef.current = withReact(withHistory(createEditor()));
  }
  const editor = editorRef.current;

  const initialValue: Descendant[] = [
    {
      type: "paragraph",
      children: [{ text: "A line of text in a paragraph." }],
    },
  ];

  const [value, setValue] = useState<Descendant[]>(initialValue);

  const customRenderer = useCallback((props) => {
    if (props.element.type === "paragraph") {
      return <Text {...props} />;
    } else if (props.element.type === "code") {
      return <Code {...props} />;
    }
  }, []);

  const formatCodeBlock = () => {
    const [match] = Editor.nodes(editor, {
      match: (n: Node) => {
        console.log(n);

        return n.type === "code";
      },
    });

    Transforms.setNodes(
      editor,
      { type: match ? "paragraph" : "code" },
      { match: (n) => Editor.isBlock(editor, n) }
    );
  };

  return (
    <Box>
      <Heading>Editor</Heading>
      <Box p={2} border="1px solid" borderColor="slate.100" rounded="md">
        <Box py={2}>
          <IconButton
            icon={<RiCodeLine />}
            onClick={formatCodeBlock}
            aria-label="sold"
            size="sm"
          />
        </Box>
        <Divider />
        <Slate
          editor={editor}
          value={value}
          onChange={(newValue) => setValue(newValue)}
        >
          <Editable renderElement={customRenderer} />
        </Slate>
      </Box>
    </Box>
  );
};

export default RichEditor;

About this issue

  • Original URL
  • State: open
  • Created 2 years ago
  • Reactions: 9
  • Comments: 16 (3 by maintainers)

Most upvoted comments

I think you should find the answer here.

I think you should find the answer here.

The type error still happens on the latest version, 0.77.2, even after using the Editor.isBlock or Element.isElement type predicates. BaseElement doesn’t have a type property.

TypeScript playground example:

import { BaseElement, Editor, Element, Node } from "slate";

declare const editor: Editor;
declare const node: Node;

if (Editor.isBlock(editor, node)) {
   node.type;
   //   ~~~~
   // Property 'type' does not exist on type 'BaseEditor | BaseElement'.
   //   Property 'type' does not exist on type 'BaseEditor'.
}

if (Element.isElement(node)) {
   node.type;
   //   ~~~~
   // Property 'type' does not exist on type 'BaseEditor | BaseElement'.
   //   Property 'type' does not exist on type 'BaseEditor'.
}

declare const baseElement: BaseElement;

baseElement.type;
//          ~~~~
// Property 'type' does not exist on type 'BaseElement'.

The next recommendation in the official documentation is not fully working https://docs.slatejs.org/concepts/12-typescript#migrating-from-0.47.x

You may occur with an error after narrowing types using Element.isElement(node)

Property 'type' does not exist on type 'BaseEditor | BaseElement'

To solve this you need to extend slate types like that

// custom.d.ts

import { BaseElement } from 'slate';

declare module 'slate' {
  export interface BaseElement {
    type: string;
  }
}

Quick workaround: Change:

const [match] = Editor.nodes(editor, {
  match: (n: Node) => {
    return n.type === "code";
  },
});

to

const [match] = Editor.nodes(editor, {
  match: (n: Node) => {
    return (n as any).type === "code";
  },
});

For this problem, the solution is simple and actually documented in official docs

import {Element as SlateElement} from 'slate'
 if (e.key === "`" && e.ctrlKey) {

              const [match] = Editor.nodes(editor, {
                match: (n) => SlateElement.isElement(n) && n.type === "code",
              });

              Transforms.setNodes(
                editor,
                {
                  type: match ? "paragraph" : "code",
                },
                {
                  match: (n) =>
                    SlateElement.isElement(n) && Editor.isBlock(editor, n),
                }
              );

You need to check if node is block like this:

match: n => Editor.isBlock(editor, n) && n.type === 'code'

@rahulnyk you need to narrow down the type of the Node to Element. You can do that by applying a condition, either an if or by already suggested simple conjunction, e.g.:

'type' in node && node.type === 'someType'
//                  -> TS now knows `type` property exists

or

Element.isElement(node) &&  node.type === 'someType'

The second example might fail if your custom Element type doesn’t have the type property. The first one should work 100 %.

Quick workaround: Change:

const [match] = Editor.nodes(editor, {
  match: (n: Node) => {
    return n.type === "code";
  },
});

to

const [match] = Editor.nodes(editor, {
  match: (n: Node) => {
    return (n as any).type === "code";
  },
});

Thanks for the help.

Mine was actually for bold. I found a workaround for this by first extending the BaseEditor and then creating a new type for Text and Element.

type CustomElement = {
type: "paragraph";
children: CustomText[];
bold?: boolean | null;
};

type CustomText = { bold?: boolean | null};

interface CustomBaseEditor extends BaseEditor {
text?: string;
bold?: boolean | null;
}

declare module "slate" {
interface CustomTypes {
Editor: CustomBaseEditor & ReactEditor;
Element: CustomElement;
Text: CustomText;
}}