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

Update findDocumentOrShadowRoot to return the root node instead of throwing unnecessarily #4427

Merged
merged 8 commits into from
Aug 10, 2021

Conversation

ben10code
Copy link
Contributor

@ben10code ben10code commented Aug 9, 2021

Description
In Slate v0.62, a new util method getDocumentOrShadowRoot was introduced. This latter returns the ShadowRoot or Document node in which the editor is rendered but throws when not found.

Unable to find DocumentOrShadowRoot for editor element: [object HTMLDivElement]

This seems to be causing issues for consumers who render the editor within React portals. The issue arises at unmount time, because getRootNode returns the now-detached portal node.

Version 0.61.3 does not have this issue and seems to be the go-to for most right now.

Issue
Fixes: #4267

Example
In this first example, the editor is rendered inside a modal that uses React portal. When first opening the modal, it works fine - but closing the modal (unmounting) and reopening will crash because root isn't an instance of Document or ShadowRoot anymore.

https://codesandbox.io/s/slate-reproduction-reakit-portal-forked-yeykl?file=/index.js:902-907

In this second example, the portal is created using createPortal and then appended to the document later. In the initial render, the root node is not the Document and throws. (Example by JackRobards)

https://codesandbox.io/s/slate-reproduction-reakit-portal-fk002

Context
This pull request is straightforward and shouldn't introduce unwanted behaviours. Instead of throwing when the root isn't an instance of Document or ShadowRoot, I simply return undefined. In places where the root node is used, I simply check whether it exists before performing operations.

I've been trying to gather context surrounding the reasons why we throw, but couldn't find anything. Feel free to share if you have context 🙏 !

Checks

  • The new code matches the existing patterns and styles.
  • The tests pass with yarn test.
  • The linter passes with yarn lint. (Fix errors with yarn fix.)
  • The relevant examples still work. (Run examples with yarn start.)
  • You've added a changeset if changing functionality. (Add one with yarn changeset add.)

@changeset-bot
Copy link

changeset-bot bot commented Aug 9, 2021

🦋 Changeset detected

Latest commit: a37ecb6

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
slate-react Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copy link
Collaborator

@dylans dylans left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @ben10code . This has very much been a problem for me as well in production and I was just about to tackle it so I really appreciate it. And the second commit really helped make this easier to review so thanks for that as well.

The general approach looks fine to me. I'm going to ask others on the Slate team for a quick review and hopefully we can land this quickly.

Also if you could add a changeset that would be great (helps us automate the release notes). You can run yarn changeset or in your branch and it will help you generate one. Thanks!

packages/slate-react/src/components/editable.tsx Outdated Show resolved Hide resolved
packages/slate-react/src/components/editable.tsx Outdated Show resolved Hide resolved
packages/slate-react/src/plugin/react-editor.ts Outdated Show resolved Hide resolved
throw new Error(
`Unable to find DocumentOrShadowRoot for editor element: ${el}`
)
return undefined
Copy link
Collaborator

@dylans dylans Aug 9, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking back at https://github.com/ianstormtaylor/slate/pull/3749/files , I think this may just suppress things, rather than restoring support for window/window.document. Perhaps better would be:

Suggested change
return undefined
return ReactEditor.getWindow(editor).document

And then we wouldn't need the change to support | undefined throughout? Unless I'm missing something (e.g. if window wasn't actually working previously). And perhaps throw if that is also not defined?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the correct fix given what used to happen before #3749, and after it's merged every other change (to deal with potentially undefined return value) can be reverted.

I also suggest mentioning in the changeset that this fixes a regression caused by #3749.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good to go now!

throw new Error(
`Unable to find DocumentOrShadowRoot for editor element: ${el}`
)
return ReactEditor.getWindow(editor).document
Copy link
Collaborator

@clauderic clauderic Aug 10, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this could be simplified to:

Suggested change
return ReactEditor.getWindow(editor).document
return el.ownerDocument

Though to be honest, I think the entire method could be re-written, the logic is hard to follow currently.

Something like:

findDocumentOrShadowRoot(editor: ReactEditor): Document | ShadowRoot {
  const el = ReactEditor.toDOMNode(editor, editor)
  const root = el.getRootNode()

  if ((root instanceof Document || root instanceof ShadowRoot) && root.getSelection != null) {
    return root
  }
  
  return el.ownerDocument
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated, indeed much cleaner! Also tested affected examples to ensure they still work :)

@ben10code ben10code changed the title Update findDocumentOrShadowRoot to return undefined instead of throwing unnecessarily Update findDocumentOrShadowRoot to return the root node instead of throwing unnecessarily Aug 10, 2021
@dylans dylans merged commit 3f69a9f into ianstormtaylor:main Aug 10, 2021
@github-actions github-actions bot mentioned this pull request Aug 10, 2021
dylans pushed a commit to dylans/slate that referenced this pull request Sep 13, 2021
…throwing unnecessarily (ianstormtaylor#4427)

* Update `findDocumentOrShadowRoot` to return undefined instead of throwing unnecessarily

* Small refactoring to improve the diff for reviewers

* Add changeset for patch

* Update new-trainers-peel.md

* Resolve PR comments

* Revert undefined checks, return window.document and update changeset

* Simplify findDocumentOrShadowRoot based on PR feedback

* Re-run CI

Thanks everyone for your review and thanks @ben10code for your first contribution!
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

slate-react 0.62.0+ - Crash if initial render's root node is not the document (reakit Portal)
4 participants