codesandbox/sandpack

Changing decorators recreates Codemirror

danieldelcore opened this issue · 1 comments

Scenario

Updating the decorators array moves the cursor to the top of the file.

Cause

This is because decorators are specified as part of the following dependency array.

This results in a full re-instantiation of the codemirror instance, which resets state, extensions etc.

Possible fix

Instead changes to decorators need to be updated in a way that does not require a full re-instantiation.

Something like this might work, however i have limited knowledge of codemirror so this just adds new decorators and doesn't clear old ones.

    React.useEffect(() => {
      if (cmView.current && sortedDecorators) {
        // Add new hightlight decorators
        cmView.current.dispatch({
          effects: StateEffect.appendConfig.of([
            highlightDecorators(sortedDecorators),
          ]),
        });
      }
    }, [sortedDecorators]);

Repro

I've created the following story which can be pasted into this file:
sandpack/sandpack-react/src/components/CodeEditor/CodeMirror.tsx

If you modify the file a new highlight is generated at random. You will notice that the cursor will be reset after every keypress.

type Decorators = Array<{ className: string; line: number }>;

stories.add("DecoratorsDynamic", () => {
  const [decorators, setDecorators] = React.useState<Decorators>([
    // { className: "highlight", line: 9 },
  ]);
  const [file, setFile] = React.useState(`const people = [{
  id: 0,
  name: 'Creola Katherine Johnson',
  profession: 'mathematician',
}, {
  id: 1,
  name: 'Mario José Molina-Pasquel Henríquez',
  profession: 'chemist',
}];

export default function List() {
  const [text, setText] = useState("")
  const listItems = people.map(person =>
    <li>{person}</li>
  );
  return <ul>{listItems}</ul>;
}`);

  React.useEffect(() => {
    const lines = file.split("\n");

    setDecorators([
      {
        className: "highlight",
        line: Math.floor(Math.random() * lines.length),
      },
    ]);
  }, [file]);

  return (
    <SandpackProvider>
      <style>
        {`.highlight, .widget {
        background: red;
      }`}
      </style>
      <CodeEditor
        code={file}
        decorators={decorators}
        fileType="jsx"
        initMode="immediate"
        onCodeUpdate={(newCode) => setFile(newCode)}
      />
    </SandpackProvider>
  );
});

Pushed my changes here incase that helps ☝️