react-email-editor icon indicating copy to clipboard operation
react-email-editor copied to clipboard

Renders twice with latest nextjs

Open ari-motors-team opened this issue 3 years ago • 5 comments

The default implementation renders twice using nextjs.

I used this example: https://github.com/unlayer/react-email-editor

and got this outcome image

package.json

{
  "name": "polyma",
  "version": "2.0.0",
  "private": true,
  "license": "The Unlicense",
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint",
    "fauna": "fgu"
  },
  "dependencies": {
    "@apollo/client": "^3.5.10",
    "@nivo/bar": "^0.79.1",
    "@nivo/core": "^0.79.0",
    "@nivo/line": "^0.79.1",
    "@tailwindcss/line-clamp": "^0.4.0",
    "framer-motion": "^6.3.0",
    "graphql": "^16.3.0",
    "next": "12.1.6",
    "react": "^18.0.0",
    "react-dnd": "^16.0.0",
    "react-dnd-html5-backend": "^16.0.0",
    "react-dom": "^18.0.0",
    "react-email-editor": "^1.6.0",
    "react-hook-form": "^7.30.0",
    "react-ranger": "^2.1.0",
    "react-use-copy-to-clipboard": "^1.0.1",
    "uuid": "^8.3.2"
  },
  "devDependencies": {
    "@fortawesome/fontawesome-svg-core": "^6.1.1",
    "@fortawesome/free-brands-svg-icons": "^6.1.1",
    "@fortawesome/free-regular-svg-icons": "^6.1.1",
    "@fortawesome/free-solid-svg-icons": "^6.1.1",
    "@fortawesome/react-fontawesome": "^0.1.18",
    "autoprefixer": "^10.4.4",
    "eslint": "^8.12.0",
    "eslint-config-next": "12.1.6",
    "fauna-gql-upload": "^2.4.3",
    "faunadb": "^4.5.4",
    "postcss": "^8.4.12",
    "sharp": "^0.30.3",
    "tailwind-scrollbar": "^1.3.1",
    "tailwindcss": "^3.0.24",
    "vercel": "^24.1.0"
  }
}

Full code Email.js

import { useRef } from "react";
import dynamic from "next/dynamic";

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

export default function Email() {
  const emailEditorRef = useRef(null);

  function saveDesign() {
    emailEditorRef.current.editor.saveDesign((design) => {
      console.log("saveDesign", design);
    });
  }

  function exportHtml() {
    emailEditorRef.current.editor.exportHtml((data) => {
      const { design, html } = data;
      console.log("exportHtml", html);
    });
  }

  function onLoad() {
    // editor instance is created
    // you can load your template here;
    // const templateJson = {};
    // emailEditorRef.current.editor.loadDesign(templateJson);
  }

  function onReady() {
    // editor is ready
    console.log("onReady");
  }

  return (
    <div className="">
      <div>
        <button onClick={saveDesign}>Save Design</button>
        <button onClick={exportHtml}>Export HTML</button>
      </div>

      <div className="h-screen overflow-hidden">
        <EmailEditor
          ref={emailEditorRef}
          onLoad={onLoad}
          onReady={onReady}
          // works for now, the second email builder gets hidden.
          // minHeight="100vh"
        />
      </div>
    </div>
  );
}

ari-motors-team avatar May 10 '22 14:05 ari-motors-team

The iframe is injected twice. image

ari-motors-team avatar May 17 '22 14:05 ari-motors-team

Hello, I was having the same problem in React V18. And I saw that the problem was caused by the fact that Strict Mode causes the element to be rendered twice in React. I solved the problem by removing the StricMode tag in index.js.

root.render(
    <> // Without StrictMode 
     <App />
    </>
);

furkanmeclis avatar May 18 '22 19:05 furkanmeclis

Solved it for me. However, that means there is some legacy code running. I'll leave it open for now. :)

ari-motors-team avatar May 19 '22 10:05 ari-motors-team

Having the same issue today, I really feel like turning off StrictMode is not a good workaround though.

For anyone that does not mind this, turning it off does indeed fix the issue.

In case anyone is wondering why turning of StrictMode fixes the issue https://stackoverflow.com/a/61897567/12035491

driaug avatar Jul 13 '22 19:07 driaug

My dirty solution

const removeUnusedIframes = () => {
  const frames = document.getElementsByTagName('iframe');
  const unlayerFrames = Array.from(frames).filter((frame) => frame.src.includes('unlayer'));
  if (unlayerFrames.length === 1) return;
  unlayerFrames.slice(1).forEach((frame) => frame.remove());
};

itishermann avatar Sep 02 '22 15:09 itishermann

This should be resolved with the merge. Thanks @otaviosoares

adeelraza avatar Oct 26 '22 22:10 adeelraza

Hey guys, I'm running into the same issue here:

import React, { useRef, useState, useEffect } from "react";
import dynamic from "next/dynamic";
import type { NextPage } from "next";

// Assuming the EmailEditorProps and EditorRef are correctly exported from react-email-editor
import { EditorRef } from "react-email-editor";
import templateJson from "./utils/emailTemplate.json";

const EmailEditor = dynamic(
    () => import("react-email-editor").then((mod) => mod.EmailEditor),
    { ssr: false }
);

const TemplateEditor: NextPage = (props: any) => {
    const emailEditorRef = useRef<EditorRef>(null);
    const [isReady, setIsReady] = useState(false);
    const initialized = useRef(false);

    const exportHtml = () => {
        console.log("exportHtml");
        const unlayer = emailEditorRef.current?.editor;
        unlayer?.exportHtml((data) => {
            const { design, html } = data;
            console.log("exportHtml", html);
        });
    };

    const onReady = (unlayer: any) => {
        console.log("onReady");
        if (!initialized.current) {
            unlayer.loadDesign(templateJson);
            setIsReady(true);
            initialized.current = true;
        }
    };

    const onLoad = (unlayer: any) => {
        console.log("onLoad");
        unlayer.init({
            id: "editor-container",
            displayMode: "email",
            customJS: [
                "https://examples.unlayer.com/examples/simple-custom-tool/custom.js",
            ],
        });
    };

    useEffect(() => {
        const handleReady = (unlayer: any) => {
            console.log("handleReady");
            // Ensure the editor is ready before loading the design
            unlayer.loadDesign(templateJson);
        };

        const addEventListener = () => {
            console.log("addEventListener");
            if (emailEditorRef.current) {
                emailEditorRef.current.editor?.addEventListener(
                    "ready",
                    handleReady
                );
            }
        };

        const removeEventListener = () => {
            if (emailEditorRef.current) {
                emailEditorRef.current.editor?.removeEventListener("ready");
            }
        };

        addEventListener();

        // Return the cleanup function to remove the event listener
        return removeEventListener;
    }, [emailEditorRef.current]);

    return (
        <div id="editor-container" className="flex h-screen">
            {isReady && (
                <div>
                    <button onClick={exportHtml}>Export HTML</button>
                </div>
            )}{" "}
            <EmailEditor
                editorId="email-editor"
                ref={emailEditorRef}
                onLoad={onLoad}
                onReady={onReady}
                style={{ height: "100%" }}
            />
        </div>
    );
};

export default TemplateEditor;

qixxin avatar Apr 12 '24 02:04 qixxin