cursor moves to the beginning of the line while typing quickly if the CodeMirror is controlled and the render of the component containing CodeMirror takes too long
Hello! I believe I'm running into a bug. My symptoms are similar to the old https://github.com/uiwjs/react-codemirror/issues/199, but I'm using the latest version (4.23.6) and also the workaround from https://github.com/uiwjs/react-codemirror/issues/199#issuecomment-941943581 does not work for me.
So, I'm typing into my controlled <CodeMirror> and, every few keystrokes, the cursor jumps to the beginning of the text box. I believe triggering the issue is dependent on <CodeMirror> being used in a "heavy" component: if the parent of <CodeMirror> (which supplies the value) is simple, then I don't get the cursor jump. If the parent component has a bunch of other sub-components (and so the render takes longer), then I do get the bug -- so I suspect the issue is some sort of race condition whereby an update of value arrives "too late", after more key presses.
My CodeMirror invocation looks like this:
const [value, setValue] = useState("");
<CodeMirror
value={value}
onChange={(value) => setValue(value)}
/>
<MyOtherHeavyComponent/>
If I remove <MyOtherHeavyComponent/>, then it all works fine. Also, if I type slowly, it again works fine.
Yeah. This is pretty bad. I now have to type code character by character otherwise the cursor jumps to the beginning and messes everything up.
This helped me but not sure if it will help you. So I was looking at this issue and I modified the code here to also call the user provided onChange and it stopped moving the cursor to the beginning.
This is still an issue in 4.23.12 -- has anyone identified a workaround? I've tried what was suggested in #700 with no luck.
I've been debugging this issue in my app, and wanted to share some findings and ask a few questions: Context:
- using v4.22.1
- Controlled editor setup
- the
onChangeupdates a state variable - that state variable is passed back down as value (via prop drilling)
We were able to reduce the frequency of the issue by debouncing the onChange but are now trying to fix this for good.
I am manually reproducing this (in a non-debounced version) by typing rapidly - without arrow key navigation or backspace/delete.
Whenever the issue occurs, this full-document dispatch is being triggered. I'm not assuming causation, but I'm unable to come up with a case that warrants such a drastic update to the view. Because in my case the actual diff between currentValue and value was just one extra letter at the end.
My best guess is that this is a remanent from codemirror5 days So I'm wondering if that useEffect can be removed, or made smarter? Here's a crude draft - it fixed the issue for me and didn't seem to show any side effects so far
let start = 0;
const minLength = Math.min(value.length, currentValue.length);
while (start < minLength && value[start] === currentValue[start]) {
start++;
}
if (start === value.length && start === currentValue.length) {
return null;
}
let endCurrent = currentValue.length;
let endValue = value.length;
while (
endCurrent > start &&
endValue > start &&
value[endValue - 1] === currentValue[endCurrent - 1]
) {
endCurrent--;
endValue--;
}
const changesToDispatch = {
from: start,
to: endCurrent,
insert: value.slice(start, endValue),
}
view.dispatch({
changes: changesToDispatch,
annotations: [External.of(true)],
});
I'd be happy to raise and test a PR but first wanted to discuss:
- What cases was that
useEffectmeant to handle? - Can such minimal diff based update be incorporated instead?
- Or, should we explicitly start tracking the last cursor position as well and add that as well to this view update?
@nsrCodes You're very welcome to submit a PR for optimization!
This useEffect is mainly used to replace the editor content with the new value when the external value changes.
The line annotations: [External.of(true)] is typically used to mark the update as externally triggered, so that plugins or extensions can recognize and handle it accordingly.
Looking forward to your improvements! 🙌
https://github.com/uiwjs/react-codemirror/blob/154727c817478f85d267005639ab390dbf9bde6f/core/src/useCodeMirror.ts#L162-L173
@jaywcjlove raised a PR with the fix that worked for me https://github.com/uiwjs/react-codemirror/pull/739
The approach I took was to not allow any updates while someone is typing into the editor. This might not be ideal for every case, for e.g. maybe for multi user experiences (yjs based) this may lead to delays and lost updates from the non-primary user (my usage doesn't allow for this case so haven't tested this thoroughly)
But otherwise, this became the only way I could ensure that:
- the cursor doesn't jump around while the user is typing, while
- Also possible to update the editor's state from the higher level consumer
I hope this helps
@nsrCodes I tested your PR and reviewed the code carefully; I didn’t find any obvious issues. I’ll keep an eye on the potential performance impact of setInterval(..., 1).
I plan to merge the PR and release a new version.
Thank you for your contribution!
Thank you! Just wanted to report this appears to be fixed for me in the latest version. Much appreciated! Edit: I may have spoke too soon :( It seems hit or miss. Sometimes it works, sometimes it doesn't. Still trying it though.
I am still noticing this bug w/ the latest CodeMirror.
But I also noticed something strange... if I type a space character first (just hit spacebar once) on an empty instance with no code, A) Nothing appears. But also, B) I can then type normally. Very very strange.