react-codemirror icon indicating copy to clipboard operation
react-codemirror copied to clipboard

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

Open andreimatei opened this issue 1 year ago • 5 comments

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.

andreimatei avatar Oct 25 '24 20:10 andreimatei

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.

azizghuloum avatar Dec 03 '24 01:12 azizghuloum

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.

adamnieto avatar Dec 16 '24 16:12 adamnieto

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.

seansps avatar May 07 '25 21:05 seansps

I've been debugging this issue in my app, and wanted to share some findings and ask a few questions: Context:

  1. using v4.22.1
  2. Controlled editor setup
  3. the onChange updates a state variable
  4. 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:

  1. What cases was that useEffect meant to handle?
  2. Can such minimal diff based update be incorporated instead?
  3. Or, should we explicitly start tracking the last cursor position as well and add that as well to this view update?

nsrCodes avatar Jun 08 '25 18:06 nsrCodes

@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 avatar Jun 10 '25 19:06 jaywcjlove

@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:

  1. the cursor doesn't jump around while the user is typing, while
  2. Also possible to update the editor's state from the higher level consumer

I hope this helps

nsrCodes avatar Jun 27 '25 03:06 nsrCodes

@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!

jaywcjlove avatar Jun 27 '25 06:06 jaywcjlove

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.

seansps avatar Jul 01 '25 01:07 seansps

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.

seansps avatar Sep 10 '25 01:09 seansps