我想使用
<div onInput={onChange} contentEditable>
来像文本区域一样保存用户输入。
我听说我需要使用 onInput 侦听器在输入更改时触发 fn 。
问题是当我用 React 状态更新文本时,它会不断地将插入符号移动到文本开头
这是我的代码
import React from "react";
const App = () => {
const [value, setValue] = React.useState("I am edittable");
const onChange = (e) => {
const html = e.target.innerHTML;
setValue(html);
};
return (
<div onInput={onChange} contentEditable>
{value}
</div>
);
};
export default App;
代码沙盒 https://codesandbox.io/s/react-editable-cell-pwil6?file=/src/App.js:0-310
我该如何解决它?
我的建议是使用 onInput,但不要使用状态变量作为可编辑元素的innerHTML。
设置一些默认值,contentEditable将处理innerHTML的变化,而onInput将根据innerHTML的值处理状态的变化。
如果你想确保innerHTML不会为空,你可以添加 onBlur 事件,该事件检查它是否为 ==="" 并将用默认值或其他值更新它,等等。
这已经很旧了,但我刚刚自己解决了这个问题,我想我会分享它。
onChange 永远不会触发,而 onInput 也不是最好的方法。你需要用 onFocus 和 onBlur 来模拟 onChange。
onFocus发生时将innerHTML的值复制到变量。
当onBlur发生时,针对变量测试innerHTML以查看它是否发生变化。如果是这样,请执行 onChange 操作。你需要这样的东西:
const onFocus = (e) => {
val_start = e.target.innerHTML;
};
const onBlur = (e) => {
if(val_start !== e.target.innerHTML) {
//Whatever you put here will act just like an onChange event
const html = e.target.innerHTML;
setValue(html);
}
};
contentEditable
将使 div
内的内容可编辑。
您可以使用
onInput
代替使用onBlur
,这样闪烁的光标就不会不断移动到文本开头。
您对文本所做的任何更改都将在屏幕上可见,并且一旦您将焦点移出元素,状态就会更新。
<div onBlur={onChange} contentEditable>
{value}
</div>
注意:下面的代码是使用 zod 的较长代码的简化版本。为了简单起见,我删除了一些部分。
export const EmailForm: React.FC = () => {
const {register, handleSubmit, formState: {errors}, setValue} = useForm();
// Optional: for when the <label> gets clicked
const emailRef = useRef<HTMLDivElement>(null);
const onSubmit = async (data: FieldValues) => {
// Send data here. In catch statement use setError or formError
console.log(data);
}
const onInput = (e: FormEvent<HTMLInputElement>) => {
setValue('email', e.currentTarget.innerText);
}
return (
<form onSubmit={handleSubmit(onSubmit)}>
{/* The actual field which react hook form validates */}
<input type="hidden" {...register('email')} />
<div>
<label onClick={() => emailRef.current?.focus()}>Email</label>
<div contentEditable onInput={onInput} ref={emailRef}
role="textbox" tabIndex={0}></div>
<div>{errors.email?.message}</div>
</div>
<div>
<button type="submit">Submit</button>
</div>
</form>
);
}
本质上,您想要的是使 div 表现得像
<textarea>
,同时保留功能。这样做的好处各不相同,但我通常这样做,这样它就会在用户键入时自动扩展。
由于这是 React,我使用 RHF 来管理内容,因此我们需要在用户键入时填充一个隐藏字段。这也可以是
<textarea>
,但只需确保它不可见即可。
<input type="hidden" {...register('email')} />
A
<div>
不知道什么是 onChange
事件,但它对 onInput
事件响应良好,因此请使用它。还添加 role
和 tabIndex
以帮助提高可用性,如 在此 so 中所示。
<label onClick={() => emailRef.current?.focus()}>Email</label>
<div contentEditable onInput={onInput} ref={emailRef}
role="textbox" tabIndex={0}></div>
可选地,如果您想让
<label>
在像标签一样单击时聚焦在 <div>
上,则应使用 useRef
使其正常工作:
const emailRef = useRef<HTMLDivElement>(null);
现在,每次用户输入内容时,
onInput
回调都会触发setValue
函数,允许 RHF 访问数据,就像访问任何其他字段一样。这反过来又有助于生成错误消息:
<div>{errors.email?.message}</div>
最后,当提交表单时,会触发
onSubmit
回调。从那里您可以将其发送到您的服务器。
这就是你需要具备的:
<div onChange={onChange} contentEditable>
而不是
onInput
应该是 onChange
演示:https://codesandbox.io/s/react-editable-cell-forked-ey7o1?file=/src/App.js