I'm trying to set up a controlled contentEditable
in React. Every time i write something in the div the component re-renders, and the cursor/caret jumps back to the beginning. I'm trying to deal with this by saving the cursor in an onInput
callback:
import { useState, useEffect, useRef, useLayoutEffect } from 'react'
function App() {
const [HTML, setHTML] = useState()
const [selectionRange, setSelectionRange] = useState()
console.log('on rerender:', selectionRange)
useLayoutEffect(() => {
console.log('in layout effect', selectionRange)
const selection = document.getSelection()
if (selectionRange !== undefined) {
selection.removeAllRanges()
selection.addRange(selectionRange)
}
})
function inputHandler(ev) {
console.log('on input', document.getSelection().getRangeAt(0))
setSelectionRange(document.getSelection().getRangeAt(0).cloneRange())
setHTML(ev.target.innerHTML)
}
return (
<>
<div
contentEditable
suppressContentEditableWarning
onInput={inputHandler}
dangerouslySetInnerHTML={{ __html: HTML }}
>
</div>
<div>html:{HTML}</div>
</>
)
}
export default App
This doesn't work, the cursor is still stuck at the beginning. If I input one character in the contentEditable
div, i get the output:
on input
Range { commonAncestorContainer: #text, startContainer: #text, startOffset: 1, endContainer: #text
, endOffset: 1, collapsed: true }
on rerender:
Range { commonAncestorContainer: #text, startContainer: #text, startOffset: 1, endContainer: #text
, endOffset: 1, collapsed: true }
in layout effect
Range { commonAncestorContainer: div, startContainer: div, startOffset: 0, endContainer: div, endOffset: 0, collapsed: true }
Why does the value of selectionRange
change in the useLayoutEffect
callback, when it was correct at the start of the re-render?