outline / rich-markdown-editor

The open source React and Prosemirror based markdown editor that powers Outline. Want to try it out? Create an account:
https://www.getoutline.com
BSD 3-Clause "New" or "Revised" License
2.87k stars 588 forks source link

Using as a Controlled Component #517

Closed ecumene closed 3 years ago

ecumene commented 3 years ago

https://reactjs.org/docs/forms.html#controlled-components

To Reproduce Steps to reproduce the behavior:

const [value, setValue] = useState();
return <Editor value={value} onChange={(getChanges) => setValue(getChanges())}

Typing any letters makes the cursor jump back to the start of the document, writing backwards

Expected behavior I expect using Editor as a controlled component to work. The reason I want this is that I want to be able to clear the text box after you submit.

Version latest

Desktop (please complete the following information):

What's the best way to clear the text in the meantime? Is there a way to access the ProseMirror object underneath via a ref somehow?

tommoor commented 3 years ago

Changing the value prop completely resets the state which includes the selection state. The answer is to not use it as a controlled component in this way, it's not within scope of the project to change this behavior.

I recommend adding a key prop that you change on submission to remount the editor for the usecase you've described

ecumene commented 3 years ago

Have you considered adding a ref for the inner Prose context so I could clear it that way?

Something like

const editorState = useRef();

const handleSubmit = () => editorState.clear(); // ???

return <Editor ref={editorState}/>

Would this work?

tommoor commented 3 years ago

You should be able to access everything prosemirror related through the existing ref, ref.view.state

sekoyo commented 3 years ago

@tommoor What is the recommended way to change the content when switching to a new document? I found that value and defaultValue do nothing on their own but using a key causes a memory leak within RME:

const [value, setValue] = useState('')
const [key, setKey] = useState(0)

useEffect(() => {
  if (selectedDoc) {
    setValue(selectedDoc.content)
    setKey(k => k + 1)
  }
}, [selectedDoc])

return (
  <Editor
    key={key}
    defaultValue={value} // Or value
  />
)

It works fine switching documents but on load causes a memory leak:

index.js:1 Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.
    at RichMarkdownEditor
sekoyo commented 3 years ago

Nevermind I found a happy medium which is to not load the component until there is a value:

const [value, setValue] = useState<string | null>(null)
const [key, setKey] = useState(0)

useEffect(() => {
  if (selectedDoc) {
    setValue(selectedDoc.content)
    setKey(k => k + 1)
  }
}, [selectedDoc])

return value !== null && <Editor key={key} defaultValue={value} />
tommoor commented 3 years ago

key is recommended, although value alone should work depending on your usecase.