React - contentEditable 属性的 onChange 事件

问题描述 投票:0回答:5

我想使用

  <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

我该如何解决它?

reactjs input contenteditable
5个回答
3
投票

这有一个解决方案,但 onChange 不注册该事件。它不会改变状态。不要使用它!

我的建议是使用 onInput,但不要使用状态变量作为可编辑元素的innerHTML。

设置一些默认值,contentEditable将处理innerHTML的变化,而onInput将根据innerHTML的值处理状态的变化。

如果你想确保innerHTML不会为空,你可以添加 onBlur 事件,该事件检查它是否为 ==="" 并将用默认值或其他值更新它,等等。


1
投票

这已经很旧了,但我刚刚自己解决了这个问题,我想我会分享它。

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);
    }
  };

0
投票

contentEditable
将使
div
内的内容可编辑。

您可以使用

onInput
代替使用
onBlur
,这样闪烁的光标就不会不断移动到文本开头。

您对文本所做的任何更改都将在屏幕上可见,并且一旦您将焦点移出元素,状态就会更新。

<div onBlur={onChange} contentEditable>
  {value}
</div>

0
投票

使用 React Hook Form 2023.

实现 Typescript。

tldr;下面为那些匆忙的人完成了代码。

注意:下面的代码是使用 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 Hook 表单注册

由于这是 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
回调。从那里您可以将其发送到您的服务器。


-1
投票

这就是你需要具备的:

<div onChange={onChange}  contentEditable>

而不是

onInput
应该是
onChange

演示:https://codesandbox.io/s/react-editable-cell-forked-ey7o1?file=/src/App.js

© www.soinside.com 2019 - 2024. All rights reserved.