import React, { useEffect, useState } from "react"; import "./styles.css"; const RichText = () => { const [value, setValue] = useState(""); useEffect(() => { const div = document.getElementById("textarea"); if (div) { setTimeout(() => { div.focus(); }, 0); } }); return ( <div className="rich-text" onInput={(e) => { setValue(e.target.innerText); }} contentEditable id="textarea" dangerouslySetInnerHTML={{ __html: value }} /> ); }; export default RichText; Quiero implementar un componente de texto enriquecido, la idea es que puede escribir texto dentro de algún campo, puede diseñar este texto (ponerlo en negrita, cursiva, subrayado, etc.). Quiero el mismo valor de texto dentro de la variable de estado de value , y luego envolverlo de alguna manera dentro de las etiquetas html <p>Hello <b> Andrew</b></p> , y mostrarlo en tiempo real dentro del mismo campo, con estilo . Para mostrar etiquetas html dentro de div , campo contentEditable , necesito usar dangerouslySetInnerHTML SetInnerHTML, y este es el problema principal. Que en cada botón presionado, actualizo el valor, el componente luego se vuelve a representar, el foco va al comienzo del campo, pero quiero que esté al final en el momento en que escribe el texto nuevo. Intenté hacerlo con ref => ref.current.focus() , no funciona, en el código anterior puedes ver que también intento hacerlo con vanilla js usando timeout , tampoco funciona work, autoFocus : solo se puede usar en input, textarea, etc , div se puede usar con esta propiedad. Lo guardé en ref , pero luego no puedo mostrar html envuelto dentro de div . Intenté muchos casos, pero es parecido. ¿Alguna idea de cómo hacerlo?
El problema es cuando se usa el gancho useState con contentEditable y dangerouslySetInnerHTML SetInnerHTML para sincronizar el valor. Cuando escribe algo en el div, se vuelve a representar y devuelve el cursor al inicio.
Puede usar variables de instancia en el componente de la función ( useRef para actualizar el valor) para deshacerse del problema.
Y debe usar innerHTML en lugar de innerText para guardar la cadena HTML
Prueba como a continuación
import React, { useRef } from "react"; import "./styles.css"; const RichText = () => { const editableRef = useRef(null); const { current: value } = useRef( '<div style="color:green">my initial content</div>' ); const setValue = (e) => { value.current = e.target.innerHTML; }; const keepFocus = () => { const { current } = editableRef; if (current) { current.focus(); } }; return ( <div className="rich-text" onInput={setValue} contentEditable id="textarea" ref={editableRef} onBlur={keepFocus} dangerouslySetInnerHTML={{ __html: value }} /> ); }; export default RichText;import React from "react"; import "./styles.css"; class TextInput extends React.Component { constructor(props) { super(); this.state = { value: "" }; } shouldComponentUpdate() { return true; } componentDidUpdate() { const el = document.getElementById("textarea"); if (el) { console.log(el.selectionStart, el.selectionEnd); var range, selection; if (document.createRange) { //Firefox, Chrome, Opera, Safari, IE 9+ range = document.createRange(); //Create a range (a range is a like the selection but invisible) range.selectNodeContents(el); //Select the entire contents of the element with the range range.collapse(false); //collapse the range to the end point. false means collapse to end rather than the start selection = window.getSelection(); //get the selection object (allows you to change selection) selection.removeAllRanges(); //remove any selections already made selection.addRange(range); //make the range you have just created the visible selection } else if (document.selection) { //IE 8 and lower range = document.body.createTextRange(); //Create a range (a range is a like the selection but invisible) range.moveToElementText(el); //Select the entire contents of the element with the range range.collapse(false); //collapse the range to the end point. false means collapse to end rather than the start range.select(); //Select the range (make it the visible selection } } } update(value) { this.setState({ value }); } render() { console.log(this.state.value); return ( <div className="rich-text" onInput={(e) => { this.update(e.target.innerText); }} contentEditable id="textarea" dangerouslySetInnerHTML={{ __html: `<b>${this.state.value}</b>` }} /> ); } } export default TextInput;