ueberdosis / tiptap

The headless rich text editor framework for web artisans.
https://tiptap.dev
MIT License
27.34k stars 2.27k forks source link

TypeError: Cannot read property 'matchesNode' of null (TipTap for React) #1451

Closed chasinhues closed 3 years ago

chasinhues commented 3 years ago

Description

Same issue as in https://github.com/ueberdosis/tiptap/issues/438. TypeError that crashes my app.

Steps to reproduce the bug

Happens on occasion when navigating to a page with a TipTap editor on it.

CodeSandbox

In the CSB below, run the tests, and observe the console spits out a bunch of these type errors for the browser. I don't know why it does this...

https://codesandbox.io/s/tiptap-prose-matchesnode-error-yhvdc

image

Expected behavior

No error.

Environment?

Additional context

I tried to keep the CSB example above as close to my current code as possible, minus other third-party libraries that would unnecessarily bloat the example.

I am able to replicate the error consistently in my tests:

    Error: Uncaught [TypeError: Cannot read property 'matchesNode' of null]
        at reportException (/Users/jasonhughes/sites/innovator-portal/innovator/client/node_modules/jsdom/lib/jsdom/living/helpers/runtime-script-errors.js:62:24)
        at Timeout.task [as _onTimeout] (/Users/jasonhughes/sites/innovator-portal/innovator/client/node_modules/jsdom/lib/jsdom/browser/Window.js:521:9)
        at listOnTimeout (internal/timers.js:549:17)
        at processTimers (internal/timers.js:492:7) TypeError: Cannot read property 'matchesNode' of null
        at EditorView.updateStateInner (/Users/jasonhughes/sites/innovator-portal/innovator/client/node_modules/prosemirror-view/src/index.js:141:45)
        at EditorView.updateState (/Users/jasonhughes/sites/innovator-portal/innovator/client/node_modules/prosemirror-view/src/index.js:114:10)
        at Editor.dispatchTransaction (/Users/jasonhughes/sites/innovator-portal/innovator/client/node_modules/@tiptap/core/src/Editor.ts:299:15)
        at EditorView.dispatch (/Users/jasonhughes/sites/innovator-portal/innovator/client/node_modules/prosemirror-view/src/index.js:374:50)
        at Object.method [as focus] (/Users/jasonhughes/sites/innovator-portal/innovator/client/node_modules/@tiptap/core/src/CommandManager.ts:35:18)
        at /Users/jasonhughes/sites/innovator-portal/innovator/client/node_modules/@tiptap/core/src/Editor.ts:90:21
        at Timeout.task [as _onTimeout] (/Users/jasonhughes/sites/innovator-portal/innovator/client/node_modules/jsdom/lib/jsdom/browser/Window.js:516:19)
        at listOnTimeout (internal/timers.js:549:17)
        at processTimers (internal/timers.js:492:7)
hanspagel commented 3 years ago

Did you try upgrading all your dependencies? The core is at beta.81 already.

MaximeHeckel commented 3 years ago

Same issue on my end

Updated all my dependencies to the latests to try out, the issue still shows up.

For me it starts at "@tiptap/react": "^2.0.0-beta.43", which introduces the dependency array for the editor.

chasinhues commented 3 years ago

Thanks for the suggestion. Issue still persists using: "@tiptap/core@^2.0.0-beta.81" "@tiptap/react@^2.0.0-beta.46" "@tiptap/starter-kit@^2.0.0-beta.77"

obezzad commented 3 years ago

@chasinhues is there any workaround?

chasinhues commented 3 years ago

Not that I'm aware of

obezzad commented 3 years ago

@chasinhues I hope the following would help spotting the root cause.

This issue happens to me in editor.setContent() after hot-reload and when creating an editor dynamically, it also happens when dynamically adding multiple editors without delay. If I wait for a second after creating an editor before re-creating another one, it doesn't occur.

It seems that successive dynamic creation error is due to setTimeout(() => editor.createNodeViews(), 0) in EditorContent. I also suspect that this error causes the setContent() one too.

image

I added try-catch around setContent() with 3 retries separated by a delay of 100ms. However, the createNodeViews() does not seem to be catchable from the component.

Is there any chance of a hotfix to prevent this uncaught error from propagating since the software will be shipped soon :( ?

jamesopti commented 3 years ago

We're hitting this and have updated our dependencies to all the latest tiptap versions as of 2 days ago (see below):

This may be happening when we destroy the editor and then recreate it again. Is there any guidance on timing for destroying the editor before instantiating a new one?

➜  client git:(main) yarn info @tiptap/core @tiptap/react
├─ @tiptap/core@npm:2.0.0-beta.84
│  ├─ Version: 2.0.0-beta.84
│  │
│  └─ Dependencies
│     ├─ @types/prosemirror-commands@npm:^1.0.4 → npm:1.0.4
│     ├─ @types/prosemirror-inputrules@npm:^1.0.4 → npm:1.0.4
│     ├─ @types/prosemirror-keymap@npm:^1.0.4 → npm:1.0.4
│     ├─ @types/prosemirror-model@npm:^1.13.0 → npm:1.13.0
│     ├─ @types/prosemirror-schema-list@npm:^1.0.3 → npm:1.0.3
│     ├─ @types/prosemirror-state@npm:^1.2.6 → npm:1.2.6
│     ├─ @types/prosemirror-transform@npm:^1.1.3 → npm:1.1.3
│     ├─ @types/prosemirror-view@npm:^1.17.1 → npm:1.17.1
│     ├─ prosemirror-commands@npm:^1.1.9 → npm:1.1.9
│     ├─ prosemirror-inputrules@npm:^1.1.3 → npm:1.1.3
│     ├─ prosemirror-keymap@npm:^1.1.3 → npm:1.1.4
│     ├─ prosemirror-model@npm:^1.14.2 → npm:1.14.2
│     ├─ prosemirror-schema-list@npm:^1.1.4 → npm:1.1.4
│     ├─ prosemirror-state@npm:^1.3.4 → npm:1.3.4
│     ├─ prosemirror-transform@npm:^1.3.2 → npm:1.3.2
│     └─ prosemirror-view@npm:^1.18.7 → npm:1.18.7
│
└─ @tiptap/react@npm:2.0.0-beta.50
   ├─ Instances: 1
   ├─ Version: 2.0.0-beta.50
   │
   └─ Dependencies
      ├─ @tiptap/extension-bubble-menu@npm:^2.0.0-beta.23 → npm:2.0.0-beta.23
      ├─ @tiptap/extension-floating-menu@npm:^2.0.0-beta.17 → npm:2.0.0-beta.17
      └─ prosemirror-view@npm:^1.18.7 → npm:1.18.7
philippkuehn commented 3 years ago

This patch should fix that error.

obezzad commented 3 years ago

@philippkuehn Thank you for the patch! I noticed that the same error also occurs on setContent after React's Fast Refresh.

chasinhues commented 3 years ago

Yeah I'm still noticing this as obezzad mentioned, even after upgrading to the patch with the fix.

I noticed that the same error also occurs on setContent after React's Fast Refresh.

thatsjonsense commented 3 years ago

We were able to hack around this by adding checks for editor.isDestroyed in our useEffect hooks like so: image

Could we update editor.commands to do this check automatically, and never try to run a command when the editor itself is destroyed? That might catch a lot of issues.

However, even after adding this, hot reload still triggered an error in the Yjs sync plugin, see screenshot below. It looks like that can also be suppressed by adding // @refresh reset at the top of the file where we load the Collaboration plugin. This is a NextJS directive, not sure if there are equivalents elsewhere.

Screen Shot 2021-07-06 at 2 21 29 PM

obezzad commented 3 years ago

@thatsjonsense Thank you for the workaround. I believe all the matchesNode errors are due editor.isDestroyed not checked. This may not be an issue in Vue, but React seems to work differently.

@philippkuehn Can you re-open this issue, or should we create another issue and refer to this one?

martinemmert commented 3 years ago

The CollaborationCursorPlugin throws the same error in some circumstances. We use the react TipTap package with the collab feature. Our editor can be opened and closed within our application - the open/close state is synced with all currently connected clients via websocket.

The error occurs when a user is still inside the editor, while the editor is closed by someone else. The root cause is the same as described here: https://github.com/ueberdosis/tiptap/issues/438#issuecomment-887586064

paolostyle commented 3 years ago

I have the same issue with BubbleMenu component that comes with @tiptap/react, it crashes when Fast Refresh is triggered. From what I understood from this conversation, the lack of editor.isDestroyed check causes that, so I changed this part in useEffect of BubbleMenu component (locally, in node_modules :smile:) and it seems to work:

    if (!editor.isDestroyed) {
      editor.registerPlugin(BubbleMenuPlugin({
        pluginKey,
        editor,
        element: element.current as HTMLElement,
        tippyOptions,
        shouldShow,
      }))
    }

However it would probably make more sense to check it in registerPlugin considering a similar check is present in unregisterPlugin. Changing it in BubbleMenu seems like lower risk change, though. I can prepare a PR, but I'd rather have an opinion where it should be fixed as I'm completely unfamiliar with the codebase.

longlongago2 commented 3 years ago

This patch should fix that error.

  useEffect(() => {
    if (editor && !editor.isDestroyed) {
      editor.chain().focus().setContent(content).run();
    }
  }, [content, editor]);

can solve this problem.

thatsjonsense commented 3 years ago

Hey @philippkuehn, I think I've found a more general solution to this that prevents the error. A little hacky but let me know what you think

import { EditorView } from 'prosemirror-view'

EditorView.prototype.updateState = function updateState(state) {
  if (!this.docView) return // This prevents the matchesNode error on hot reloads
  this.updateStateInner(state, this.state.plugins != state.plugins)
}
philippkuehn commented 3 years ago

@thatsjonsense Really a bit hacky :) Do you still see this error? Haven't seen it for ages.

thatsjonsense commented 3 years ago

Yep, all the time :( I believe it’s coming from inside the collaboration plug-ins during hot reload. They try to update some stage after the editor is destroyed, before it is recreated

On Fri, Oct 29, 2021 at 1:02 AM Philipp Kühn @.***> wrote:

@thatsjonsense https://github.com/thatsjonsense Really a bit hacky :) Do you still see this error? Haven't seen it for ages.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/ueberdosis/tiptap/issues/1451#issuecomment-954519094, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAQ5BWPHVOPIFENHMWJ7UXLUJJPKBANCNFSM46P7VDZA .

-- Jon Noronha Cofounder, Gamma

jasonm commented 2 years ago

I also see this regularly. I am using Snowpack with HMR and Fast Refresh. (https://www.snowpack.dev/concepts/hot-module-replacement) I have a React app with a component that wraps TipTap named <TipTapText/> in TipTapText.tsx. Each time I save that file and it HMR/Fast Refreshes, I see this error.

Adding the lines from @thatsjonsense 's comment above to TipTapText.tsx fixes the issue for me.

Versions:

 $ grep tiptap package.json
    "@tiptap/extension-collaboration": "^2.0.0-beta.31",
    "@tiptap/extension-collaboration-cursor": "^2.0.0-beta.31",
    "@tiptap/extension-placeholder": "^2.0.0-beta.44",
    "@tiptap/html": "^2.0.0-beta.142",
    "@tiptap/react": "^2.0.0-beta.95",
    "@tiptap/starter-kit": "^2.0.0-beta.145",
raarts commented 2 years ago

Experienced this as well. Not hot-reloading. Also using multiple editors on the same page. Hack by @thatsjonsense worked for me.

gustavotoyota commented 2 years ago

I modified the code by @thatsjonsense to reuse the old updateState:

import { EditorView } from 'prosemirror-view';

const oldUpdateState = EditorView.prototype.updateState;

EditorView.prototype.updateState = function (state) {
  // This prevents the matchesNode error on hot reloads
  if (!this.docView) {
    return;
  }

  oldUpdateState.call(this, state);
};

This bug was apparently fixed in y-prosemirror@1.1.0, but this same version introduced another issue: https://github.com/yjs/y-prosemirror/issues/117

So I had to downgrade to y-prosemirror@1.0.20 and this problem still exists there.

tguelcan commented 2 years ago

For me the error also came with a modal function where the editor was not visible.

I solved the problem by checking if the plugins are loaded.

export const resetContent = () => {
        if (editor.view.pluginViews.length) {
            editor.commands.clearContent();
        }
    };
branlok commented 2 years ago

I'm still getting this issue in development, for me it occurs when my styled component wrapper gets resaved - it triggers the error. I've gotten around it by extracting editor explicitly into its own component and passing back to the parent component that has the styled component wrapper.

using: vite": "^3.0.7", "@tiptap/react": "^2.0.0-beta.114", "@tiptap/starter-kit": "^2.0.0-beta.191", "styled-components": "^5.3.5",

react-dom.development.js:22839 Uncaught TypeError: Cannot read properties of null (reading 'matchesNode')
    at EditorView.updateStateInner (index.js:4913:49)
    at EditorView.update (index.js:4868:14)
    at EditorView.setProps (index.js:4882:14)
    at Editor2.createNodeViews (Editor.ts:299:15)
    at PureEditorContent.init (EditorContent.tsx:67:14)
    at PureEditorContent.componentDidMount (EditorContent.tsx:42:10)
    at invokeLayoutEffectMountInDEV (react-dom.development.js:25133:22)
    at invokeEffectsInDev (react-dom.development.js:27351:11)
    at commitDoubleInvokeEffectsInDEV (react-dom.development.js:27327:5)
    at flushPassiveEffectsImpl (react-dom.development.js:27056:5)
xiel commented 2 years ago

This error also seems to be triggered by StrictMode, because the component is immediatly unmounted and remounted.

hrasoa commented 2 years ago

From time to time we are experiencing the same issue, this workaround https://github.com/ueberdosis/tiptap/issues/1451#issuecomment-953348865 works well, but is there any other recommendations since then? Thanks

Gin-Quin commented 1 year ago

It's not completely tiptap-relateded, but I had this issue because I implemented an asynchronous ProseMirror plugin.

If I closed the editor before the asynchronous call resolved, the program tried to update a view that didn't exist anymore.

My code was something like that:

asynchronousApiCall().then(data => {
  // this could happen after my editor is closed
  view.dispatch(view.state.tr.setMeta("call resolved", data)
})

And I added the following check:

asynchronousApiCall().then(data => {
  // check if the editor is still alive
  if (!(view as any)?.docView) return
  view.dispatch(view.state.tr.setMeta("call resolved", data)
})
axelinternet commented 1 year ago

I encountered this issue when adding dependencies to useEditor from @tiptap/react. I'm running "@tiptap/react": "^2.0.0-beta.199"

If I don't add the dependencies to the array I don't get this error when I have multiple editors on the same page. As I add deps to get it to react to being moved/destroyed I get this error. I

I used https://github.com/ueberdosis/tiptap/issues/1451#issuecomment-941988769 solution and removing the dependency array. This seems like a bug to me, I'm just getting a less granular solution to what I assume useEditor does under the hood.

SalahAdDin commented 1 year ago

I encountered this issue when adding dependencies to useEditor from @tiptap/react. I'm running "@tiptap/react": "^2.0.0-beta.199"

If I don't add the dependencies to the array I don't get this error when I have multiple editors on the same page. As I add deps to get it to react to being moved/destroyed I get this error. I

I used #1451 (comment) solution and removing the dependency array. This seems like a bug to me, I'm just getting a less granular solution to what I assume useEditor does under the hood.

It has been almost two years and there is no solution in sight.

vaetas commented 1 year ago

I've just followed the official documentation for installing TipTap into NextJS and got this bug. Latest TipTip and NextJS. I just copy pasted the example code without adding anything.

I tried all fixes and nothing seems to work. (I'm not sure if I was doing correctly.) Whenever I change something in the component I see this error in the browser.

Is there any other was I could make this work?

tovaschreier commented 1 year ago

@vaetas Assuming your issue is caused by the same root cause as the issue I submitted, it was fixed in v2.0.0-beta.220!

azjgard commented 1 year ago

We were able to hack around this by adding checks for editor.isDestroyed in our useEffect hooks like so: image

Could we update editor.commands to do this check automatically, and never try to run a command when the editor itself is destroyed? That might catch a lot of issues.

However, even after adding this, hot reload still triggered an error in the Yjs sync plugin, see screenshot below. It looks like that can also be suppressed by adding // @refresh reset at the top of the file where we load the Collaboration plugin. This is a NextJS directive, not sure if there are equivalents elsewhere.

Screen Shot 2021-07-06 at 2 21 29 PM

This was ultimately the only thing that solved the problem for us. Echoing the question by @thatsjonsense: is this something that is possible for TipTap to check internally when commands are executing against an instance of the editor?

SalahAdDin commented 1 year ago

We were able to hack around this by adding checks for editor.isDestroyed in our useEffect hooks like so: image Could we update editor.commands to do this check automatically, and never try to run a command when the editor itself is destroyed? That might catch a lot of issues. However, even after adding this, hot reload still triggered an error in the Yjs sync plugin, see the screenshot below. It looks like that can also be suppressed by adding // @refresh reset at the top of the file where we load the Collaboration plugin. This is a NextJS directive, not sure if there are equivalents elsewhere. Screen Shot 2021-07-06 at 2 21 29 PM

This was ultimately the only thing that solved the problem for us. Echoing the question by @thatsjonsense: is this something that is possible for TipTap to check internally when commands are executing against an instance of the editor?

Did you open a PR for it?

I don't think they are planning to fix this.

melodyclue commented 1 year ago

This error also seems to be triggered by StrictMode, because the component is immediatly unmounted and remounted.

The remix app was also giving an error on every hot reload due to StrictMode, removing StrictMode worked fine. But I guess that doesn't make sense for StrictMode, lol.

actraiser commented 1 year ago

I am using Remix v2 and have the same problem on every hot reload. I use useEditor(). I did not see a solution/workaround for people using that hook, maybe someone solved this though?

fzsf163 commented 8 months ago

Hey @philippkuehn, I think I've found a more general solution to this that prevents the error. A little hacky but let me know what you think

import { EditorView } from 'prosemirror-view'

EditorView.prototype.updateState = function updateState(state) {
  if (!this.docView) return // This prevents the matchesNode error on hot reloads
  this.updateStateInner(state, this.state.plugins != state.plugins)
}

where do you put this line? I am getting following error . Property 'docView' does not exist on type 'EditorView' & Property 'updateStateInner' is private and only accessible within class 'EditorView'

whoselen commented 6 months ago

Hey @philippkuehn, I think I've found a more general solution to this that prevents the error. A little hacky but let me know what you think

import { EditorView } from 'prosemirror-view'

EditorView.prototype.updateState = function updateState(state) {
  if (!this.docView) return // This prevents the matchesNode error on hot reloads
  this.updateStateInner(state, this.state.plugins != state.plugins)
}

where do you put this line? I am getting following error . Property 'docView' does not exist on type 'EditorView' & Property 'updateStateInner' is private and only accessible within class 'EditorView'

https://codesandbox.io/p/sandbox/tiptap-newline-error-kpgm8?file=%2Fsrc%2FApp.tsx%3A6%2C10-6%2C20

kingsleydev19 commented 5 months ago

Experienced this as well. Not hot-reloading. Also using multiple editors on the same page. Hack by @thatsjonsense worked for me.

Hello, can you kindly explain to me exactly where I need to apply the codes? I am lost and I see this error.

stetttho commented 4 months ago

got the same error today when trying to update from 2.2.4 to 2.4.0 . workaround didn’t solve it, so switched back to 2.2.5, which is the latest version which works for me.

using multiple editors on the same page as others also mentioned.