Business
Jobs
  • About Us
  • Solutions
    • Job Postings
      Post your job and receive qualified candidates in 48h.
    • Candidate Assessments
      500+ technical and psychological tests, plus anti-fraud.
    • Headhunting
      Tailor-made executive search from start to finish.
    • Payroll + EOR
      Payroll dispersal and EOR across 15+ LATAM countries.
  • Pricing
  • Jobs

0

210
Views
Tratar con el cursor con contenido editable controlado en React

Estoy tratando de configurar un contenido controlado contentEditable en React. Cada vez que escribo algo en el div, el componente vuelve a renderizarse y el cursor/salta de intercalación al principio. Estoy tratando de lidiar con esto guardando el cursor en una devolución de llamada onInput :

 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

Esto no funciona, el cursor todavía está atascado al principio. Si ingreso un carácter en el div contentEditable , obtengo el resultado:

 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 }

¿Por qué cambia el valor de selectionRange en la devolución de llamada useLayoutEffect , cuando era correcto al comienzo de la nueva representación?

about 4 years ago · Juan Pablo Isaza
2 answers
Answer question

0

Ok, no estoy familiarizado con la operación de rango, pero me parece que el problema radica en el cambio de estado.

Puedes usar useRef o useState para arreglar esto, déjame usar un objeto con useState por ahora.

 function App() { const [HTML, setHTML] = useState() const [selectionRange, setSelectionRange] = useState({ range: null }) useLayoutEffect(() => { const selection = document.getSelection() if (selectionRange !== undefined) { selection.removeAllRanges() if (selectionRange.range) selection.addRange(selectionRange.range) } }) function inputHandler(ev) { selectionRange.range = document.getSelection().getRangeAt(0).cloneRange()) setSelectionRange({ ...selectionRange }) setHTML(ev.target.innerHTML) }

Puede reemplazar fácilmente esta versión con un useRef , el punto es asegurarse de que el valor se asigne de inmediato antes de pasar por setState , lo que lleva tiempo para que su estado se actualice al valor más reciente.

about 4 years ago · Juan Pablo Isaza Report

0

Cuando el div contentEditable se vuelve a renderizar, desaparece. El objeto Range contiene referencias a los hijos de este div (propiedades startNode , endNode ), y cuando el div desaparece, el objeto Range rastrea esto y se restablece a su padre, con compensación cero.

El siguiente código demuestra cómo lidiar con esto si sabe que el div contentEditable solo tendrá un hijo. Soluciona el problema donde el cursor se atasca al principio. Lo que hacemos es guardar el desplazamiento en el texto, y al restaurar creamos un nuevo objeto Range , con el nodo de texto recién renderizado como startNode y nuestro desplazamiento guardado como startOffset .

 import { useState, useEffect, useRef, useLayoutEffect } from 'react' function App() { const [HTML, setHTML] = useState() const [offset, setOffset] = useState() const textRef = useRef() useLayoutEffect(() => { if (offset !== undefined) { const newRange = document.createRange() newRange.setStart(textRef.current.childNodes[0], offset) const selection = document.getSelection() selection.removeAllRanges() selection.addRange(newRange) } }) function inputHandler(ev) { const range = document.getSelection().getRangeAt(0) setOffset(range.startOffset) setHTML(ev.target.innerHTML) } return ( <> <div contentEditable suppressContentEditableWarning onInput={inputHandler} dangerouslySetInnerHTML={{ __html: HTML }} ref={textRef} > </div> <div>html:{HTML}</div> </> ) } export default App
about 4 years ago · Juan Pablo Isaza Report
Answer question
Find remote jobs

Discover the new way to find a job!

Top jobs
Top job categories
Business
Post vacancy Pricing Sales
Legal
Terms and conditions Privacy policy
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Show me some job opportunities
There's an error!