I once saw an article on “What can a One-liner agent Do? Answer:
At that time tried really very fun, so every time can show a wave of operation in front of the sister, in their amazed eyes, MY heart happily smiled — well, and let a person who does not understand the technology found the United States 🐶, cough.
For a long time, I felt that this attribute existed just for the sake of existence, but after receiving the request today, I found that this attribute, which felt useless, was the perfect solution to my need.
demand
The requirements are simple, just add a button to the input field. This function is usually used for mass mail, where the button “name” is actually a variable, and the backend should automatically fill in the name of the real user, and then send the email to the user.
The problem
At first glance, this requirement seems to be accomplished using position: relative + position: Absolute. But think about it, it’s unlikely: the button will surely overwrite the input, and a single delete “name” button would be hard to do.
It is also impossible to do this with
I think another option would be to start with
at the end of
for focus and blur. But feelings are also particularly hard to achieve.
Button Inside TextArea in HTML
Then I searched again and found this library: React-Contenteditable
The solution
It was a bit of a shock to see contentEditable, a property I had been using to show off for a long time, but it was a day of relief for me.
The library is also interesting to use, because when you use function components, instead of having a value and an onChange, you need to pass an innerRef to control the text inside.
function App() {
const innerRef = useRef<HTMLElement>(null);
const value = useRef<string> (' ');
const onChange = (event: ContentEditableEvent) = > {
value.current = event.target.value;
}
const onAddButton = () = > {
if(! innerRef.current) {return;
}
innerRef.current.innerHTML += ' & NBSP; '
}
return (
<div className="App">
<ContentEditable
style={{ border: '1px solid black', height: 100 }}
innerRef={innerRef}
html={value.current}
onChange={onChange}
/>
<button onClick={onAddButton}>Add the name</button>
</div>
);
}
Copy the code
Take a look at the react-Contenteditable source code
The above mentioned use of ref to control text changes made me curious about how it was implemented, so I cloned his Github and found that the implementation was not very simple. Github is here.
Render function
Since we use contentEditable for input and output, almost any element is ok, so this component allows us to pass in a tagName to specify which element to base on.
render() {
const{ tagName, html, innerRef, ... props } =this.props;
return React.createElement(
tagName || 'div',
{
...props,
ref: typeof innerRef === 'function' ? (current: HTMLElement) = > {
innerRef(current)
this.el.current = current
} : innerRef || this.el,
onInput: this.emitChange,
onBlur: this.props.onBlur || this.emitChange,
onKeyUp: this.props.onKeyUp || this.emitChange,
onKeyDown: this.props.onKeyDown || this.emitChange,
contentEditable:!this.props.disabled,
dangerouslySetInnerHTML: { __html: html }
},
this.props.children);
}
Copy the code
The render function here is a function that specifies which function to render, binds events, whether to enable the contentEditable property, and passes props.
value
We also observe that the value here is actually displayed by dangerouslySetInnerHTML: {__html: HTML}.
We also had to think about preventing script injection when we were dangerously close, so the source code normalize values as well:
function normalizeHtml(str: string) :string {
return str && str.replace(/ |\u202F|\u00A0/g.' ');
}
Copy the code
The event
It’s also worth noting that in addition to and
For example, I tried to implement this myself:
const VarInput: FC<IProps> = (props) = > {
const{ value, tag, disabled, onInput, ref, ... restProps } = props;const innerRef = useRef(null);
const curtRef = ref || innerRef;
const emitChange = () = > {
const callbackValue: string = curtRef.current ? curtRef.current.innerHTML : ' '; onInput! (callbackValue); }constvarInputProps = { ... restProps,ref: curtRef,
contentEditable: !disabled,
onInput: emitChange,
dangerouslySetInnerHTML: { __html: value }
}
return createElement(tag || 'div', varInputProps);
}
Copy the code
When used
function App() {
const [value] = useState(' ');
const onChange = (value: string) = > {
console.log(value); / / print the value
}
return (
<div className="App">
<VarInput value={value} onInput={onChange} />
</div>
);
Copy the code
However, when I only bindonChange
Will not trigger the event! So, there is a difference between onInput and onChange!
emitChange
The onChange, onInput, and other callbacks call the emitChange function:
emitChange = (originalEvt: React.SyntheticEvent<any>) => { const el = this.getEl(); if (! el) return; const html = el.innerHTML; if (this.props.onChange && html ! == this.lastHtml) { // Clone event with Object.assign to avoid // "Cannot assign to read only property 'target' of object" const evt = Object.assign({}, originalEvt, { target: { value: html } }); this.props.onChange(evt); } this.lastHtml = html; }Copy the code
It makes a lot of sense, after all, to just get the innerHTML, construct an event, and put it in onChange. Simple.
componentDidUpdate
To be honest, I have done all the above things myself, but there is one problem I can’t do, that is, every time I type, the cursor moves to the front!! For example, IF I type “hello”, it will say: “Olleh”, what the hell is that? !
Usage:
function App() {
const [value, setValue] = useState(' ');
const onChange = (value: string) = > {
console.log(value);
setValue(value)
}
return (
<div className="App">
<VarInput value={value} onInput={onChange} />
</div>
);
}
Copy the code
And that’s because when I setValue, the cursor moves to the front, back to the source code, and it takes that into account. He used a function in componentDidUpdate to handle this:
componentDidUpdate() {
const el = this.getEl();
if(! el)return;
// Perhaps React (whose VDOM gets outdated because we often prevent
// rerendering) did not update the DOM. So we update it manually now.
if (this.props.html ! == el.innerHTML) { el.innerHTML =this.props.html;
}
this.lastHtml = this.props.html;
replaceCaret(el);
}
function replaceCaret(el: HTMLElement) {
// Place the caret at the end of the element
const target = document.createTextNode(' ');
el.appendChild(target);
// do not move caret if element was not focused
const isTargetFocused = document.activeElement === el;
if(target ! = =null&& target.nodeValue ! = =null && isTargetFocused) {
var sel = window.getSelection();
if(sel ! = =null) {
var range = document.createRange();
range.setStart(target, target.nodeValue.length);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
}
if (el instanceofHTMLElement) el.focus(); }}Copy the code
This function ensures that the cursor is moved to the last position after each update, which is naive.
shouldComponentUpdate
ShouldComponentUpdate shouldComponentUpdate shouldComponentUpdate shouldComponentUpdate shouldComponentUpdate shouldComponentUpdate shouldComponentUpdate shouldComponentUpdate shouldComponentUpdate shouldProps
The last
Next time show this attribute when you can also give this article to sister to see, learn 🐶 (escape
(after)