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

Next.js 13.4中结合forwardRef与next/dynamic使用react-email-editor的正确方式

解决Next.js中react-email-editor的Ref指向问题

在Next.js中使用react-email-editor组件时,遇到了无法通过ref获取组件实例的问题,导致无法导出编辑器内容到剪贴板。虽已通过next/dynamic禁用SSR,但emailEditorRef始终无法正确指向react-email-editor实例。

环境版本

  • react@18.2.0
  • next@13.4.19
  • react-email-editor@^1.7.9

现有代码结构

page.tsx

'use client';

import { Button } from '@/components/ui/button';
import { toast } from '@/components/ui/use-toast';
import React, { useEffect, useRef } from 'react';
import { EditorRef, EmailEditorProps } from '@/components/email-template/types';
import ForwardedEmailEditor from '@/components/email-template/email-ui';

export default function Page() {
    const emailEditorRef = useRef<EditorRef | null>(null);

    useEffect(() => {
        console.log('ref changed = ', emailEditorRef.current);
    }, [emailEditorRef]);

    const exportHtml = () => {
        const unlayer = emailEditorRef.current?.editor;
        unlayer?.exportHtml((data) => {
            const { design, html } = data;
            try {
                console.log('Copy function Running',html);
                navigator.clipboard.writeText(html);
            } catch (error) {
                console.error('Clipboard write failed:', error);
            }
        });
    };

    const onReady: EmailEditorProps['onReady'] = (unlayer) => {
        console.log('editor react-email-editor is ready', emailEditorRef.current);
    };

    return (
        <section >
            <div>
                <span>Create Email Template</span>
                <Button onClick={exportHtml}>Export HTML</Button>
            </div>
            <div>
                <ForwardedEmailEditor ref={emailEditorRef} onReady={onReady} />
            </div>
        </section>
    );
}

email-ui.tsx

'use client';

import React from 'react';
import dynamic from 'next/dynamic';
import { EmailEditorProps, EditorRef } from './types';

const DynamicEmailEditor = dynamic(() => import('react-email-editor'), { ssr: false });

const ForwardedEmailEditor = React.forwardRef<EditorRef, EmailEditorProps>((props, ref) => {
    return <DynamicEmailEditor ref={ref} {...props} />;
});

export default ForwardedEmailEditor;

types.tsx

'use client';

export type { EditorRef, EmailEditorProps } from 'react-email-editor';

解决方案

方案1:修复Ref转发逻辑

动态加载组件时,直接转发ref可能存在传递延迟或绑定问题,需在ForwardedEmailEditor中创建内部ref,并手动同步到外部ref:

修改email-ui.tsx:

'use client';

import React, { useEffect, useRef } from 'react';
import dynamic from 'next/dynamic';
import { EmailEditorProps, EditorRef } from './types';

const DynamicEmailEditor = dynamic(() => import('react-email-editor'), { ssr: false });

const ForwardedEmailEditor = React.forwardRef<EditorRef, EmailEditorProps>((props, ref) => {
  const editorRef = useRef<EditorRef>(null);

  // 同步内部ref到外部ref
  useEffect(() => {
    if (ref) {
      if (typeof ref === 'function') {
        ref(editorRef.current);
      } else {
        ref.current = editorRef.current;
      }
    }
  }, [ref]);

  return <DynamicEmailEditor ref={editorRef} {...props} />;
});

export default ForwardedEmailEditor;

方案2:通过onReady回调保存实例(更可靠)

利用react-email-editor提供的onReady回调直接获取编辑器实例,无需依赖ref:

修改page.tsx:

'use client';

import { Button } from '@/components/ui/button';
import { toast } from '@/components/ui/use-toast';
import React, { useState } from 'react';
import { EmailEditorProps } from '@/components/email-template/types';
import ForwardedEmailEditor from '@/components/email-template/email-ui';
// 导入Unlayer类型(react-email-editor内部暴露的实例类型)
import type { Unlayer } from 'react-email-editor';

export default function Page() {
  // 用状态保存编辑器实例
  const [unlayerInstance, setUnlayerInstance] = useState<Unlayer | null>(null);

  const exportHtml = () => {
    unlayerInstance?.exportHtml((data) => {
      const { html } = data;
      try {
        navigator.clipboard.writeText(html);
        toast({ title: 'HTML已成功复制到剪贴板' });
      } catch (error) {
        console.error('剪贴板写入失败:', error);
        toast({ title: '复制失败,请重试', variant: 'destructive' });
      }
    });
  };

  const onReady: EmailEditorProps['onReady'] = (unlayer) => {
    setUnlayerInstance(unlayer);
  };

  return (
    <section>
      <div>
        <span>Create Email Template</span>
        <Button onClick={exportHtml}>Export HTML</Button>
      </div>
      <div>
        <ForwardedEmailEditor onReady={onReady} />
      </div>
    </section>
  );
}

说明

方案2无需处理ref的转发问题,直接通过组件提供的回调获取实例,是更稳定的实现方式。两种方案均可解决无法获取编辑器实例的问题,根据实际需求选择即可。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 17:43:18