Odd Behavior When Using useEffect Hook to Update Editor State
See original GitHub issueDo you want to request a feature or report a bug?
Report a suspected bug with Slate, or identify an incompatibility with React hooks.
What’s the current behavior?
The editor / page crashes when pressing enter at the end of the content.
Workflow:
In production, the text editor is controlled by a React component which first feeds the editor a default value.
import { initialValueHTML } from "./editor/data/initial-value-html";
this.state = {
isLoading: false,
value: initialValueHTML,
}
...
<RichTextEditor
updateValue={this.updateValue}
value={this.state.value}
/>
When the component has downloaded a new value from the database (think something saved from a prior entry), it then sends this along to the text editor to update.
The text editor looks for the updated props.value
and uses the useEffect
hook to update the state value:
const doc = new DOMParser().parseFromString(props.value, 'text/html');
const [value, setValue] = useState(deserialize(doc.body));
const [selection, setSelection] = useState(null);
useEffect(() => {setValue(deserialize(doc.body))}, [props.value]);
const editor = useMemo(() => withLinks(withRichText(withHistory(withReact(createEditor())))), []);
return(
<Slate
editor={editor}
value={value}
selection={selection}
onChange={(value, selection) => {
setValue(value)
setSelection(selection)
props.updateValue(serialize(editor));
}}
>
...
How to reproduce:
To reproduce the issue, and see the code
behind it, navigate to:
http://studioscale-text-editor-test.s3-website-us-east-1.amazonaws.com/
You can type in the editor and format the text. Place the cursor at the end of the initial sample string, and press enter. This should cause the editor to crash.
Open up dev tools, and the root ‘break’ in the stack trace should be something akin to:
Error: Cannot get the start point in the node at path [1] because it has no start text node.
Troubleshooting:
I have noticed that this hook seems to be the culprit, and causes different behavior:
useEffect(() => {setValue(deserialize(doc.body))}, [props.value]);
When ‘looking for’ props.value
to change, the editor crashes when you press enter at the end of
the content. When left out:
useEffect(() => {setValue(deserialize(doc.body))}, []);
This editor doesn’t crash, but the editor never receives the update past the original dummy value. The idea here was to make sure the editor has something to begin with, then update it when the database pull responds - but updating the state here causes it to crash.
Environment Info:
Slate: 0.53.0 Browser: Chrome OS: Linux
What’s the expected behavior?
The editor shouldn’t crash.
I see that one of the Built in Constraints is:
All Element nodes must contain at least one Text descendant. If an element node does not contain any children, an empty text node will be added as its only child. This constraint exists to ensure that the selection’s anchor and focus points (which rely on referencing text nodes) can always be placed inside any node. With this, empty elements (or void elements) wouldn’t be selectable.
Though, I’m not sure If I’m the one breaking this constraint, or if this specific use case is odd and just hasn’t been accounted for.
Disclaimer
One of the reasons I picked up React as a front end environment was because I could write everything in classes ( I come from a C++
background ). I’ll admit the React hooks are new grounds for me, and a paradigm shift to what I’m used to (classes). Hopefully, this is just a case of my ignorance in using them, and not something wrong with Slate.
Props
Mad props to all the devs of Slate. 53 is beautiful and so much easier to understand than prior versions.
Issue Analytics
- State:
- Created 4 years ago
- Comments:5 (2 by maintainers)
Top GitHub Comments
I did some digging into this, but wasn’t able to find anything in the core that I could override or change, with lasting effect.
Specifically looking here, at line
524 - 528
.Troubleshooting:
I updated the sandbox site to mimic a database pull, then send the prop down to the editor. Just a
setTimeout()
that sends over a differenthtml
string.I can see what @ianstormtaylor is saying in regards to not updating the
selection
. I.e., place the cursor in the word ‘bold’ before the fake pull is complete, and you’ll see the location is in the wrong spot, and still focused at [0, 1].So new prop comes in, selection remains the same. If it’s in an existing part of the document that has a node structure, then it’s no big deal.
Again, go to the end of the last paragraph, press enter, it crashes as it doesn’t know where to place the selection, since a text node doesn’t exist (I think).
Newer Behavior
I did notice it’s not limited to just the end of the content. If you press enter in between to nodes it also causes the editor to crash. At first, I thought it was just at the end of the content - so advanced forwards would leave you in no-where-null-land, but it also happens within the content itself.
Thoughts:
Updating the
selection
’s state outside of the editor seems problematic withuseEffect
, if it’s updated every timeprops.value
(the html string) changes. Feels like it would make the behavior unpredictable, given that the editor should generally handle where aselection
lives. That feels a little separation of concerns-y, but I can’t think of a good way to update theselection
on aprops
change, outside the editor.I did try passing the
selection
back and forth (the same as with the actual value of the editor, usingprops.updateValue
, but it didn’t work.So not to point the finger back at Slate, but I feel the PR would be the best route - unless someone can brainstorm a good way to update
selection
outside the editor. Unfortunately, both are beyond what I can contribute.Gotcha
If the core logic changes, and then no longer crashes, but deselects the editor - how will you be able to add a new line? As in your’e done with the current paragraph, press enter, new line. This question keeps bugging me, and I can’t see a solution. Feels like you’d press enter, the console would get a message, the document would deselect, and then you’d be stuck in an endless loop?
Reading through some of the older changes, it looks as if this was handled where the editor would insert a new text node, if it ran into this - but that was frowned on by some, as it was deemed business-logic-y?
Again, changing that is beyond me - merely thinking aloud. I don’t like to bring a problem and not try a solution, but I think this is beyond what I can help with.
I believe this is because you’re updating the
value
but not theselection
, so the selection then points to a non-existent part of the document.I’d be down for a PR that changes the core logic to instead log a warning along the lines of “the selection was invalid”, and instead deselects the editor.