You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React TinyMCE配合react-hook-form调用setValue触发无限循环问题求助

问题原因
  • TinyMCE的init配置直接定义在组件的JSX属性中,每次组件重渲染都会生成全新的对象引用,TinyMCE检测到配置变更就会触发编辑器重新初始化。
  • react-hook-form的setValue方法默认会触发表单的全局重渲染,当前编辑器组件也会同步重渲染。在onEditorChange中直接调用setValue时,会形成「编辑器内容更新→调用setValue→表单重渲染→TinyMCE重新初始化→再次触发onEditorChange」的无限循环,最终导致应用崩溃、控制台报错激增。
  • 额外维护的htmlContent本地状态每次更新也会触发组件重渲染,进一步加快了循环触发频率。
解决方案

方案1:基础修复(改动最小)

  1. 将固定的init配置移到组件外部,固定引用避免触发TinyMCE重复初始化
  2. 调用setValue时添加参数,关闭不必要的重渲染触发
  3. 可以直接去掉冗余的本地htmlContent状态,直接同步内容到表单即可

修复后代码示例:

import React from 'react'
import { Editor } from '@tinymce/tinymce-react'
import { useFormContext } from 'react-hook-form'
import { HtmlEditorWrapper } from './HtmlEditor.styles'

// 将固定init配置移到组件外部
const TINYMCE_INIT = {
  height: 200,
  menubar: false,
  plugins: [
    'advlist autolink lists link image charmap print preview anchor',
    'searchreplace visualblocks code fullscreen',
    'insertdatetime media table paste code help wordcount',
  ],
  toolbar:
    'undo redo | formatselect | ' +
    'bold italic backcolor | alignleft aligncenter ' +
    'alignright alignjustify | bullist numlist outdent indent | ' +
    'removeformat | help',
  content_style:
    'body { font-family:Helvetica,Arial,sans-serif; font-size:14px }',
}

type HtmlEditorProps = {
    name: string
}

export const HtmlEditorComponent = ({ name }: HtmlEditorProps): JSX.Element => {
    const { setValue, getValues } = useFormContext()

    return (
        <>
            <HtmlEditorWrapper>
                <Editor
                    value={getValues(name) || ''}
                    apiKey={process.env.REACT_APP_TINYMCE_KEY}
                    onEditorChange={(content: string) => {
                        // 调用setValue时添加配置,避免不必要的全局重渲染
                        setValue(name, content, {
                          shouldDirty: true,
                          shouldTouch: true,
                          shouldValidate: false // 不需要实时校验可以关闭
                        })
                    }}
                    init={TINYMCE_INIT}
                />
            </HtmlEditorWrapper>
        </>
    )
}

方案2:使用Controller集成(更推荐,符合最佳实践)

用react-hook-form官方提供的Controller组件对接第三方受控组件,内部已经做了重渲染优化,不需要手动调用setValue,更稳定:

import React from 'react'
import { Editor } from '@tinymce/tinymce-react'
import { useFormContext, Controller } from 'react-hook-form'
import { HtmlEditorWrapper } from './HtmlEditor.styles'

const TINYMCE_INIT = {
  height: 200,
  menubar: false,
  plugins: [
    'advlist autolink lists link image charmap print preview anchor',
    'searchreplace visualblocks code fullscreen',
    'insertdatetime media table paste code help wordcount',
  ],
  toolbar:
    'undo redo | formatselect | ' +
    'bold italic backcolor | alignleft aligncenter ' +
    'alignright alignjustify | bullist numlist outdent indent | ' +
    'removeformat | help',
  content_style:
    'body { font-family:Helvetica,Arial,sans-serif; font-size:14px }',
}

type HtmlEditorProps = {
    name: string
}

export const HtmlEditorComponent = ({ name }: HtmlEditorProps): JSX.Element => {
    const { control } = useFormContext()

    return (
        <>
            <HtmlEditorWrapper>
                <Controller
                  name={name}
                  control={control}
                  defaultValue=""
                  render={({ field }) => (
                    <Editor
                      value={field.value}
                      apiKey={process.env.REACT_APP_TINYMCE_KEY}
                      onEditorChange={(content: string) => {
                        field.onChange(content)
                      }}
                      init={TINYMCE_INIT}
                    />
                  )}
                />
            </HtmlEditorWrapper>
        </>
    )
}

方案3:添加防抖(可选)

如果不需要内容实时同步到表单,可以对更新逻辑添加防抖,进一步降低性能消耗:

// 安装lodash后引入,或者自己实现防抖函数
import { debounce } from 'lodash'

// 组件内部定义防抖更新函数
const updateFormValue = debounce((content: string) => {
  setValue(name, content, {
    shouldDirty: true,
    shouldTouch: true
  })
}, 300)

// onEditorChange中调用
onEditorChange={(content: string) => {
  updateFormValue(content)
}}

内容的提问来源于stack exchange,提问作者walker1

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.07 13:36:03