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;
I want to implement rich text component, the idea , is that you can type text inside some field , you can style this text ( make it bold, italic, underlined , etc) . I want to same text value inside on value
state variable , and then wrap it somehow inside of html tags <p>Hello <b> Andrew</b></p>
, and show it in real time inside the same field , styled. For showing html
tags inside of div
, contentEditable
field, I need to use dangerouslySetInnerHTML
, and this is the main problem. That on each button press , I update value, component then rerendered , focus goes at the start of field , but I want it to be at the end in time you typing new text . I've tried to make it by ref
=> ref.current.focus()
, it doesn't work, in code above you can see that I also try to make it by vanilla js with using timeout , it also doesn't work , autoFocus
- can be used only on input, textarea, etc
, div can be used with this property . I have save it to ref
, but then I can't show wrapped html inside of div
. Tried a lot of cases , but it is seemles . Any ideas how to make it ?
CodePudding user response:
The issue is when using useState
hook with contentEditable
and dangerouslySetInnerHTML
to sync value. When you type something in the div it rerenders again and brings the cursor back to the start.
You can use instance variables in the function component (useRef
to update value) to get rid of the issue.
And you should use innerHTML
instead of innerText
to get the HTML string saved
Try like below
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;
CodePudding user response:
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;