slate
slate copied to clipboard
Editor Loses Focus on Tab – Must Click to Type
Do you want to request a feature or report a bug?
Bug? User Error?
What's the current behavior?
When tabbing into Slate's editor, the focus appears and then disappears.
Recreate using this minimally viable example: CodeSandbox - Slate 0.58.1 - Tab Focus Issue
- Set focus to first editor
- Type "Mama said knock you out"
- Tab (to second editor)
- Try #2
You will not be able to immediately type into the second editor; the focus will appear to be "robbed" from it and you will need to click inside in order to enter content.
Slate: 0.58.1 Browser: Chrome OS: Windows
What's the expected behavior?
Should work much like its plain ole HTML cousin: Tab Focus Issue - Pure HTML
Upon tabbing in, focus should be preserved, allowing the end user to type immediately into the editor, i.e., without clicking into it first!
Seems suspiciously similar to #3634 (if not a duplicate).
In both cases:
- Multiple editors are involved
- The first editor works as expected
- If the first editor has previously received focus, subsequent editors require explicit clicking or multiple
focus()
calls to allow typing
@babalugats76 I figured something out!
TL;DR; Workaround: Make sure that your onChange
handler does not cause a re-render if the Slate value has not changed.
I've been struggling with a similar issue. Switching from one editor to another, results in the second editor to be focused but with no selection/cursor (user has to click it again to actually be able to type). It doesn't happen between all editors though, which was puzzling.
I found a way to make your example work. See this CodeSandbox
All I changed was making your handleEditorChange
function return prevState
if the value didn't change:
const handleEditorChange = (value, field) => {
setState((prevState) => {
+ if (prevState[field].value === value) {
+ return prevState;
+ }
return {
...prevState,
[field]: {
...prevState[field],
value: value
}
};
});
};
Your example's handleEditorChange
would always return a new state object, which makes the <TabIssue>
component re-render even if the value didn't change. With my patch above, it will not re-render since the same state object is returned (React knows it doesn't need to re-render).
I made 2 even simpler examples to demonstrate the issue:
In Example 1 tabbing/clicking between the 2 editors works fine. It uses a state field for the root children array:
const [value1, setValue1] = useState([
{ type: "paragraph", children: [{ text: "" }] }
]);
// ...
<Slate editor={editor1} onChange={setValue1} value={value1}>
// ...
Notice that when onChange
/setValue1
is called, React will only re-render the <App>
component if the children array actually changed.
Example 2 has the same issue as your example. It wraps the root children array in an object:
const [value1, setValue1] = useState({
wrapped: [{ type: "paragraph", children: [{ text: "" }] }]
});
// ...
<Slate
editor={editor1}
onChange={(wrapped) => setValue1({ wrapped })}
value={value1.wrapped}
>
// ...
Notice that when onChange
is called, React will always re-render the <App>
component even if nothing changed, since it sets a new {wrapped}
object.
I believe the issue occurs because blurring an editor also triggers it's onChange
before the onChange
is also triggered for the next editor when it gets focused. This means when editor 1 is focused and you tab (or click) to editor 2, then editor 1's state is first changed, which causes a re-render, and that somehow trips up Slate's ability to select anything in the second editor.
Not sure why... I'm looking through the Slate codebase to see if there's a possible fix so we don't need this workaround.
The issue happens even with just 1 editor: If the <Slate>
component's parent component is re-rendered in the same "tick"(?) as it gets focused. CodeSandbox example with just 1 editor
@sebastianseilund Not sure about chromium but that fix doesn't work in firefox
It is the default behavior of Tab, you can prevent in onKyedown
handler
const onKeyDown = (e) => {
if (e.key === "Tab") {
e.preventDefault();
}
};
<Slate
......
>
<Editable
onKeyDown={onKeyDown}
......
/>
</Slate>