Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Property 'type' does not exist on type 'Node' when trying to apply custom formatting #4915

Open
jonahallibone opened this issue Mar 25, 2022 · 16 comments
Labels

Comments

@jonahallibone
Copy link

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;
@idChef
Copy link

idChef commented Mar 28, 2022

I think you should find the answer here.

@eden-lane
Copy link
Contributor

You need to check if node is block like this:

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

@JoshuaKGoldberg
Copy link
Contributor

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'.

@mlshv
Copy link

mlshv commented May 31, 2022

Hi! Are there any workarounds for this?

@andreyvital
Copy link

Hey @JoshuaKGoldberg did you find any solution to this?

@JoshuaKGoldberg
Copy link
Contributor

Nope

@ducfilan
Copy link

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";
  },
});

@dyakovk
Copy link

dyakovk commented Jul 8, 2022

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;
  }
}

@jamiuoibikunle
Copy link

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;
}}

@amitGithub14
Copy link

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.

@RedHoodBoi
Copy link

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),
                }
              );

@newsiberian
Copy link

my solution:

export type LinkifyElement = {
  type: 'link';
  url: string;
  children: Text[];
};

declare module 'slate' {
  interface CustomTypes {
    Editor: LinkifyEditor;
    Element: LinkifyElement | BaseElement;
    Text: LinkifyElement | BaseText;
  }
}

later:

const [link] = Editor.nodes(editor, {
  match: (n) => 'type' in n && n.type === 'link',
});

...

const Element = ({ attributes, children, element }: RenderElementProps) => {
  const editor = useSlateStatic();

  if ('type' in element) {
    if (element.type === 'link') {
      return editor.linkElementType({ attributes, children, element });
    }
  }
  return <p {...attributes}>{children}</p>;
};

@rahulnyk
Copy link

rahulnyk commented Mar 7, 2024

None of the answers solves the original problem.
In the documentation, it is mentioned

The Node union type represents all of the different types of nodes that occur in a Slate document tree.

 type Node = Editor | Element | Text

type Descendant = Element | Text
type Ancestor = Editor | Element

And yet, any way I fetch a node using Editor.next, Editor.parent, Editor.above, Editor.node, etc. I always get the same type error:

Property 'type' does not exist on type 'Node'.
  Property 'type' does not exist on type 'BaseEditor & ReactEditor & HistoryEditor'

I am tempted to use (node as typeof ElementType) like hacks, but that may be even worse than the original problem.

Any suggestions?

@maral
Copy link

maral commented Mar 14, 2024

@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 %.

@rahulnyk
Copy link

rahulnyk commented Mar 15, 2024

Thanks @maral
I figured it out after posting the comment...
It was slightly confusing to me in the beginning, but I believe this makes a lot of sense once I figured out the node-element dichotomy :)

We need better documentation here.

Also, if I haven't said it already... thank you to all the contributors for Slate :)

@Heryk13
Copy link

Heryk13 commented Mar 27, 2024

this worked for me.

import { useMemo } from 'react';
import { createEditor, Descendant, BaseEditor } from 'slate';
import { Slate, Editable, withReact, ReactEditor } from 'slate-react';
import { withHistory, HistoryEditor } from 'slate-history';

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

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

interface TextEditorProps {
  editorContent: string;
}

const TextEditor: React.FC<TextEditorProps> = ({ editorContent }) => {
  const editor = useMemo(() => withReact(withHistory(createEditor())), []);
  const initialValue: Descendant[] = [{ type: 'paragraph', children: [{ text: editorContent }] }];

  return (
    <>
      <button onClick={() => editor.undo()}>Undo</button>
      <button onClick={() => editor.redo()}>Redo</button>
      <Slate editor={editor} initialValue={initialValue}>
        <Editable />
      </Slate>
    </>
  );
};

export default TextEditor;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests