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

Created on 2 Apr 2020  路  13Comments  路  Source: ianstormtaylor/slate

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.

bug discussion selection

Most helpful comment

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.

All 13 comments

Thanks for posting this issue.

I think this is caused by the selection state of the editor briefly being in a state that isn't possible to achieve in its value state because they are being applied out of sync.

So, understanding that the onChange here is contrived and not going to usually include setTimeout (?), my recommendation is to ensure that your onChange loop is _as tight as possible_ and if you must really do something that takes a meaningful amount of time, subscribe to onChange only to get updates and don't pass that value back to slate so it can continue to control its own internal value

One "fix" for this bug is to move the selection state back into the value state. However, that's a non trivial change and wont be done any time soon, if at all. It was a deliberate decision to keep the selection state internal.

But a lot of people have these issues so maybe over time it'll be enough to justify that transition

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 (Chrome Latest). Typing too fast (intentionally) will cause the problem above.

In my case, I'm updating the cache inside useEffect hook, React blames the component though.

Wonder if there's a fix here.

I feel like Slate should wait to move the cursor until the new value has been received, I think Quill does the same

I would argue that this is by design. Why would you want your UI to have an intentional lag for your user when they type? If you need to do anything async with your editor value you should separate it from the user's UI state. In my Slate implementations I do something like this:

  const [value, setValue] = useState(/* ... */)
  const onChange = newValue => {
    setValue(newValue);
    if (newValue !== value) {
      // set newValue in redux state, post to server etc.
    }
  }

As pointed out by @phamstack, this state mismatch can happen even on seemingly synchronous operations.

React state updates are by nature asynchronous, so there's isn't any guarantee that they will run quickly enough to satisfy Slate's requirements.

The possible solutions I can think of are:

  1. Make Slate keep an internal value state, that gets kept in sync with what's passed in by props (difficult);
  2. Make Slate wait to run selection operations until the content is updated (may introduce lag);
  3. Make Slate silently fail - gracefully handle these errors;

While you are correct that React updates are by nature asynchronous, Slate uses the ReactDOM.unstable_batchedUpdates helper to mitigate that:
https://github.com/ianstormtaylor/slate/blob/master/packages/slate-react/src/plugin/with-react.ts#L86-L100

If you could point out in a code-sandbox a "seemingly asynchronous" operation that causes this issue we could see if there is anything to fix here.

I haven't a reproduction example for that case, maybe @phamstack can provide one?

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.

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

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.

@gabriel-peracio , could you provide a little bit more information about how you're handling these external transformations? If you're going about this by mutating the value manually via immer and injecting it back in with setValue (for instance) there's a lot of state consistency logic that is being bypassed that you may need to make sure to restore yourself.

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.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

ianstormtaylor picture ianstormtaylor  路  3Comments

ianstormtaylor picture ianstormtaylor  路  3Comments

YurkaninRyan picture YurkaninRyan  路  3Comments

vdms picture vdms  路  3Comments

JSH3R0 picture JSH3R0  路  3Comments