slate: Delayed setState(value) results in Cannot resolve a DOM point from Slate point

Do you want to request a feature or report a bug?

What’s the current behavior?

Repro: https://codesandbox.io/s/slate-reproductions-1sbiz

Kapture 2020-04-02 at 17 24 18

Slate: 0.57.1 Browser: Chrome OS: Mac

What’s the expected behavior?

The above repro shows how a delayed setState(value) call systematically results in the error Cannot resolve a DOM point from Slate point.

In my case, I’m using Apollo to keep some text in sync with the server, I have optimistic updates in place, but it’s still not quick enough to not trigger this error.

I also encountered the same issue while trying to sync with some quite large (and badly written) Redux store.

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 10
  • Comments: 28 (4 by maintainers)

Commits related to this issue

Most upvoted comments

This is a problem when doing collaborative editing. Say user A has focus on line 2. User B removes the line above (line 1). As far as I can tell there is no way I can adjust the selection for user A before editable (https://github.com/ianstormtaylor/slate/blob/master/packages/slate-react/src/components/editable.tsx#L162) in slate-react tries to update the selection itself and fails because now line 2 no longer exists.

So far I have forked slate-react and just fail silently, and then adjust the selection myself.

It’s still needed to read the editor contents, but yes value should only be provided on startup. If we could make it readonly, we would.

Modifications to value must be done via Slate APIs - and only via the APIs - I’m not sure where our documentation falls over trying to explain this but clearly it does.

Hi guys.

I’m pretty new with Slate (a few weeks), but the current behavior looks reasonable for me. I’d be grateful if you can explain what’s wrong with it.

So, how I see it:

  • slate editor object contains the state of the editor
  • editor.children contains value while editor.selection contains the current state of selection
  • editor.children is exposed outside via value and onChange <Editor> props and not updated by slate itself
  • editor.selection is purely internal stuff, which is clear as the selection can be made over a couple of nodes.
  • this means that a developer is responsible to keep editor value in immediate sync with the slate
  • also, this means that if a developer changes the value from outside, he’s responsible to update the selection as well.

Would be great to hear which of the points above aren’t correct and why.

Same issue here with remote updates as @skogsmaskin & @gabriel-peracio described, as current workaround I’m removing selection during the remote update (collaboration mode or history updates), and set it back once value updated:

    React.useEffect(() => {
        setValue((currentValue) => {
            /**
             * Slate can't properly handle selection range for async updates.
             * https://github.com/ianstormtaylor/slate/issues/3575
             *
             * We need to reset selection only for real remote updates
             * (collaboration or undo/redo events)
             * In case of direct update currentValue will be update before remote
             * so they will be equal.
             */
            if (currentValue !== remoteValue) {
                editor.selection = null;
            }

            return remoteValue;
        });
    }, [editor, remoteValue]);

    React.useEffect(() => {
        if (elementSelectedForEdit && !editor.selection) {
            const lastCharPosition = Editor.end(editor, []);
            Transforms.select(editor, lastCharPosition);
            ReactEditor.focus(editor);
        }
    }, [editor, elementSelectedForEdit, value]);

In this example it’s expected that focus will be moved to the end of the string even if update happens somewhere in between (I think that this ux is better in the case if you have batching logic for remote updates), but in case if you want to keep last selection position, just store it (i.e. the ref object) and check in second hook that this selection valid for current range and set after.

But in general I think this issue should be fixed in the slate itself.

Slate core has a couple of ways to deal with this:

  • both document contents (editor.children) and selection (editor.selection) are simple properties and can be changed at the same time.
  • in collaboration, when an operation is applied to the editor, it will automatically update the selection (along with all other Ref instances).

So perhaps the React editor should expose the ability to include selection in the state, as the core does, but I also wonder if we need some documentation around getting started with collaboration. The Slate design goal is (as far as I can tell) to synchronise based on operations, not state, for the best user experience.

I have the same problem described by @skogsmaskin. My application already has collaboration built-in, which powers other widgets (slate is just one of them). I am opting out of slate’s collaborative handling and instead piggybacking on my own app’s infrastructure to make things more uniform

This error bites me constantly - when collaborating and also when using undo/redo (which, again, works through my own app code for consistency rather than using slate’s system).

User types paragraph A, then types paragraph B, then clicks undo. Paragraph A no longer exists, the selection is invalid, slate crashes.

I also cannot store/reset the selection as part of the undo/redo mechanism, unless I use slate’s system.

As far as I can tell, the value prop is really only meant to be used as an initial value. When applying a different value prop (one that was not returned from onChange) later on this can cause all kinds of problems because editor.selection, and the history stack from slate-history (if you use that) can still contain values related to the old slate value. Perhaps it’s a good idea to rename the value prop to initialValue instead?

Might also need to rework the slate provider code in https://github.com/ianstormtaylor/slate/blob/main/packages/slate-react/src/components/slate.tsx a bit so it actually directly handles a re-render instead of doing this through the parent component (changing the value causes key to change which will cause a rerender).

@rohankeskar19 there isnt a bug to fix here really, its that you need not try to intercept or delay the onChange event.

But in collaboration when I pass new value from database, It causes issues with the selection, Is there a workaround for that?

It sounded like @czechdave was requesting a reproduction case that we would expect to behave more or less synchronously. I wonder if this would serve: https://codesandbox.io/s/condescending-surf-rzcin.

It’s essentially the boilerplate from the React docs, except that it stores editor state in a Recoil atom. While Slate works as expected with editor state managed by React’s useState hook, moving the editor value to Recoil’s useRecoilState hook crashes with “Cannot resolve DOM point from Slate point.”

Recoil is new and a little funky, but its updates normally happen in the same lifecycle as useState. It would be reasonable to expect its behavior to be synchronous-ish.

An option to silently fail, and maybe fallback to the closest valid position would probably a great idea