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

Code examples cause lots of re-renders due to redefintions of `extensions` and `onChange`

Open jaller94 opened this issue 3 years ago • 1 comments

The following code example in the ReadMe will cause a lot of unnecessary re-renders, because of prop changes.

import CodeMirror from '@uiw/react-codemirror';
import { javascript } from '@codemirror/lang-javascript';

export default function App() {
  return (
    <CodeMirror
      value="console.log('hello world!');"
      height="200px"
      extensions={[javascript({ jsx: true })]}
      onChange={(value, viewUpdate) => {
        console.log('value:', value);
      }}
    />
  );
}

Instead, arrays and objects should be cached, avoiding the need to re-render or at least reducing the need for DOM changes (e.g. dispatching events, updating event handlers).

  1. Define extensions outside of App() or use useMemo() to avoid unnecessary redefinitions.
  2. Wrap the onChange handler in useCallback(… , []) to indicate that it doesn't change.
import CodeMirror from '@uiw/react-codemirror';
import { javascript } from '@codemirror/lang-javascript';

// Define the extensions outside the component so the array and objects inside stay the same on every re-render.
const extensions = [javascript({ jsx: true })];

export default function App() {
  return (
    <CodeMirror
      value="console.log('hello world!');"
      height="200px"
      extensions={extensions}
      onChange={useCallback((value, viewUpdate) => {
        console.log('value:', value);
      }, [])}
    />
  );
}

This is likely one of the reasons why the component has been reported to be slow in #294.

jaller94 avatar Apr 29 '22 10:04 jaller94

@jaller94 👍 thx!

jaywcjlove avatar Apr 29 '22 12:04 jaywcjlove

Note: if you're not passing any extensions, it goes back to being slow. Even if you don't want to add any extensions, you should still declare an empty const extensions = [] outside your component and pass it to CodeMirror.

And as @jaller94 pointed out, you will have to do both - use useCallback for onChange and have a global extensions array.

AgarwalPragy avatar Aug 21 '22 20:08 AgarwalPragy

Additionally, the demo posted with this repo https://uiwjs.github.io/react-codemirror/ suffers from the slowdown. Try holding backspace in the codemirror window in the demo - the code gets deleted in chunks instead of in a smooth letter by letter fashion.

@jaywcjlove any possibility of having @jaller94 's solution as the default behavior? Thanks :)

AgarwalPragy avatar Aug 21 '22 20:08 AgarwalPragy

@jaller94 Upgrade v4.11.5

jaywcjlove avatar Aug 22 '22 07:08 jaywcjlove

@jaller94 Upgrade v4.11.5

I see that you've added a useCallback wrapper in the new version but that doesn't fix it for me.

I've upgraded to "@uiw/react-codemirror": "^4.11.5" but I still have to wrap the onChange with a useCallback myself to keep the performance.

AgarwalPragy avatar Aug 23 '22 17:08 AgarwalPragy

@AgarwalPragy How should I handle this? PRs are welcome.

jaywcjlove avatar Aug 24 '22 00:08 jaywcjlove

@jaywcjlove

  1. I recommend to revert your change as described in #373.
  2. Here is the PR you asked for: #372

It's not your component code that's inefficient. It's the code example in the ReadMe. If you're unsure why, I recommend reading: https://www.debugbear.com/blog/react-rerenders#passing-objects-as-props

jaller94 avatar Aug 24 '22 11:08 jaller94