Slate: Last instance steals focus when multiple instances are present

Created on 23 Dec 2019  Â·  15Comments  Â·  Source: ianstormtaylor/slate

Do you want to request a _feature_ or report a _bug_?


Bug

What's the current behavior?

In my use case, I have two instances present, one for making edits, and another for previewing the result.

When multiple instances are present, and you make edits to any instance but the last in the DOM, the last instance steals focus. It then becomes impossible to focus any instance but the last.

I know this issue was fixed before, but perhaps it resurfaced post-0.50.0.

output2

Codesandbox: https://codesandbox.io/s/spike-slate-u8zpz

Slate: 0.57.1
Browser: Chrome
OS: Mac

What's the expected behavior?

When multiple instances are present, the user should be able to interact and edit an instance without another instance stealing focus.

Are there any potential workarounds?

In my use case, I only require one instance to be editable, so ensuring that this instance is last in the DOM is working for me.

bug ♥ help

Most helpful comment

As @CameronAckermanSEL said, to refer the same value occurs this bug.
In my case, I need two or more editor components that don't need shared texts, but they refer the same value as an initial value for useState like the following.

const initialValue = [{ type: "paragraph", children: [{ text: "" }] }];

const Component = () => {
  const editor = React.useMemo(() => withReact(createEditor()), []);
  const [value, setValue] = React.useState(initialValue);
  ...
};

In the above codes, initialValue is an object and the same objects have the same addresses in JavaScript, so I got this bugs. useState can accept a function which returns an initial value, I can give a function to it in order to prevent this bug.

https://codesandbox.io/s/slate-focus-bugs-8tsgy

In other cases, some custom plugins for Slate.js have an expensive process, so I made it to be memoized, but memoization shares the same value between caches. In the end, I stopped using memoization for these plugins.

All 15 comments

@ianstormtaylor is the potential fix for this in slate or slate-react?

Hi, I have the same issue, any update?

Same issue. Can't use current release

We have a bit different issue. Again 2 different instances of editor but the problem is that when clicking focus directly from one to another both seem to loose focus.

slate2

Hey @Haaxor1689, I checked PR #3506 again and it seems that everything is fine in this branch.
I can not reproduce your issue, would you please put a sample code in codepen.io or similar websites?
check-focus-again

I'm pretty sure this bug is specifically caused by reusing the same value in two different editor components. The code sandbox provided shows (maybe) a reasonable case where you'd want to do this but I'm suspecting there's a pretty good chance that this is avoidable in a lot of other use cases where the value does not need to be shared between the editor components.

Double check that each editor is initialized with a fresh value object as opposed to having a single const for it.

Unfortunately for this problem I have to stick to 0.47 for React Bricks CMS.

I created a Sandbox to test this issue:
https://codesandbox.io/s/cool-resonance-n4p1l

@ianstormtaylor Do you have an eta for this?
Thank you very much for the great library.

I see now that maybe PR 3506 should fix this?
https://github.com/ianstormtaylor/slate/pull/3506

As @CameronAckermanSEL said, to refer the same value occurs this bug.
In my case, I need two or more editor components that don't need shared texts, but they refer the same value as an initial value for useState like the following.

const initialValue = [{ type: "paragraph", children: [{ text: "" }] }];

const Component = () => {
  const editor = React.useMemo(() => withReact(createEditor()), []);
  const [value, setValue] = React.useState(initialValue);
  ...
};

In the above codes, initialValue is an object and the same objects have the same addresses in JavaScript, so I got this bugs. useState can accept a function which returns an initial value, I can give a function to it in order to prevent this bug.

https://codesandbox.io/s/slate-focus-bugs-8tsgy

In other cases, some custom plugins for Slate.js have an expensive process, so I made it to be memoized, but memoization shares the same value between caches. In the end, I stopped using memoization for these plugins.

@jagaapple thanks very much for providing a working example of how to sidestep this issue :)

@jagaapple yes, this seems to be the issue.

Cloning the initial value with JSON.stringify and then JSON.parse seems to resolve the issue for me as well.

@marcandrews Although I don't have enough space here to describe fully, but I recommend to stop using JSON.stringify and JSON.parse in order to clone objects deeply because the way has some issues. I recommend to use libraries such as https://github.com/pvorb/clone to do it.

I've created a reproduction here: https://codesandbox.io/s/slate-reproductions-c3ydd?file=/index.tsx

This is caused by KEY_TO_ELEMENT being keyed only by the Slate node (regardless of what editor it's in) to a DOM node.

The last rendering of the Slate value "wins" and its DOM node is stored:

https://github.com/ianstormtaylor/slate/blob/93fe25151722343488e9002a0ebd8ed3ba66ee95/packages/slate-react/src/components/element.tsx#L117

https://github.com/ianstormtaylor/slate/blob/93fe25151722343488e9002a0ebd8ed3ba66ee95/packages/slate-react/src/components/text.tsx#L50


This means that toDOMNode returns the last time that node was used regardless of what editor is passed to it:

https://github.com/ianstormtaylor/slate/blob/93fe25151722343488e9002a0ebd8ed3ba66ee95/packages/slate-react/src/plugin/react-editor.ts#L207


I ran into this problem because pressing the left/right arrow would move the selection to a readOnly version. This fails due to the following code path:

https://github.com/ianstormtaylor/slate/blob/93fe25151722343488e9002a0ebd8ed3ba66ee95/packages/slate-react/src/components/editable.tsx#L187

https://github.com/ianstormtaylor/slate/blob/93fe25151722343488e9002a0ebd8ed3ba66ee95/packages/slate-react/src/components/editable.tsx#L184

https://github.com/ianstormtaylor/slate/blob/93fe25151722343488e9002a0ebd8ed3ba66ee95/packages/slate-react/src/plugin/react-editor.ts#L277

https://github.com/ianstormtaylor/slate/blob/93fe25151722343488e9002a0ebd8ed3ba66ee95/packages/slate-react/src/plugin/react-editor.ts#L224

which brings us back to KEY_TO_ELEMENT.get at the top of this comment.


I believe this can be fixed just by ensuring that KEY_TO_ELEMENT uses both the editor instance and the Slate node when storing/retrieving mappings.

It's worth noting that I think many of these WeakMaps have the same issue of not being scoped to an editor instance.

@pottedmeat Wrapping your value with JSON.parse(JSON.stringify(value)) fixes your Codesandbox, so I think @CameronAckermanSEL and @jagaapple are still correct in that the problem is encountered when the value is shared between two separate instances.

@marcandrews I agree that cloning the value prevents the problem from happening but I would still consider it a bug that these maps don't take the editor instance into account.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

ianstormtaylor picture ianstormtaylor  Â·  3Comments

JSH3R0 picture JSH3R0  Â·  3Comments

adrianclay picture adrianclay  Â·  3Comments

varoot picture varoot  Â·  3Comments

gorillatron picture gorillatron  Â·  3Comments