Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OnLoad fires before api is consumed from ref #100

Open
ZeroDarkThirty opened this issue Dec 10, 2019 · 5 comments
Open

OnLoad fires before api is consumed from ref #100

ZeroDarkThirty opened this issue Dec 10, 2019 · 5 comments

Comments

@ZeroDarkThirty
Copy link

I'm trying to set merge tags onLoad and it works when the componenet loads for the first time inside a popup. However, if I close the popup and reopen it, the onLoad fires before the api can be set locally via ref, thus leading to it being undefined.

I have a working example showing what the issue is: https://codesandbox.io/s/react-modal-rjzgc

export default ({ isOpen, onRequestClose }) => {
  let editorApi;

  const setMergeTags = () => {
    editorApi.setMergeTags({
      first_name: {
        name: "First Name",
        value: "{{first_name}}"
      },
      last_name: {
        name: "Last Name",
        value: "{{last_name}}"
      }
    });
  };

  return (
    <Modal
      isOpen={isOpen}
      onRequestClose={onRequestClose}
      contentLabel="Modal"
      className={{
        base: "modal-base",
        afterOpen: "modal-base_after-open",
        beforeClose: "modal-base_before-close"
      }}
      overlayClassName={{
        base: "overlay-base",
        afterOpen: "overlay-base_after-open",
        beforeClose: "overlay-base_before-close"
      }}
      shouldCloseOnOverlayClick={true}
      closeTimeoutMS={2000}
    >
      <EmailEditor
        ref={designer => (editorApi = designer)}
        onLoad={setMergeTags}
      />
      <button onClick={onRequestClose}>Close</button>
    </Modal>
  );
};
@kwayebopp
Copy link

I had a similar issue when using react-email-editor in a functional component. You have two options:

  1. Convert your code to a class component and read side effects of onLoad implementation #22 , and especially this comment.
  2. Try using the vanilla JS version of the email editor. I describe a minimal setup in using EmailEditor inside of FunctionalComponent does not work correctly #108.

IMHO, its much faster to do 2. Good luck!

@abury
Copy link

abury commented May 7, 2020

I'm having this issue as well, it looks like the component isn't really designed to work with functional components, which is a shame

@andrewlorenz
Copy link

andrewlorenz commented Jun 16, 2020

how about this (working) pattern. Ignore using onLoad completely and just rely on the DOM object status via a ref, waiting until it exists and has an 'editor' object attached. Note some of the coding was required to allow the component to be remounted in the same component tree:

import React, { useEffect } from 'react';
import UnLayerEditor from 'react-email-editor';

const EmailEditor = ({ editorRef, templateJSON }) => {

  let timer = null;

  useEffect(() => {
    if (!loadTemplate()) timer = setInterval(loadTemplate, 500);
  }, []);

  const loadTemplate = () => {
    const myEditorRef = editorRef();  // no parameter = return the latest ref
    if (myEditorRef && myEditorRef.editor) {
      if (timer) clearInterval(timer);
      myEditorRef.editor.loadDesign(templateJSON);
      return true;
    }
    return false;
  }

  return (
    <UnLayerEditor
      ref={editorRef}  /* this will effectively call editorRef passing its reference */
    />
  );
};

export default EmailEditor;

In my example I'm passing in a getter/setter function for the editor dom object, as opposed to a local ref.

  const editorRef = (ref) => {
    if (ref) this.editorRef = ref;
    return this.editorRef;
  }

  <EmailEditor editorRef={editorRef} templateJSON={fetchedJSON} />

This means, when my controlling component wants to save, it can just call for the current design state via that ref which it owns:

const onSaveData = async() => {
    this.editorRef.exportHtml(async(data) => {
      const { design, html } = data;
      // and off we go to the database
    });
};

@pattersongarth
Copy link

pattersongarth commented Feb 8, 2021

You can change the loaded event handler to;

editorLoaded(v) {  
    let LoopsLeft: number = 10;
    let LoopingInterval = 500;
    let interval = setInterval(() => {
      if (LoopsLeft <= 0) return;//tried for 5 seconds and nothing happened so abandon attempt
      if (this.emailEditor.editor != null) {
        //stop the interval
        clearInterval(interval);
        // load the design json here

      }
      else {
        LoopsLeft--;
      }
    }, LoopingInterval);
}

this should solve the problem

@andrewlorenz
Copy link

I've actually just implemented this even more tidily. Also uses the 'design:updated' event to fire an 'onChange' event from the component which can be used to track change / store changes to template.

import React, { useRef, useCallback } from 'react';
import EmailEditor from 'react-email-editor';

// onChange: ({ design, html })

const Editor = ({ design, onChange }) => {

  const editorRef = useRef();

  const onLoadEditor = useCallback(() => {
    const timer = setInterval(function() {
      if (editorRef && editorRef.current && editorRef.current.editor) {
        editorRef.current.editor.loadDesign(design);
        editorRef.current.addEventListener('design:updated', () => editorRef.current.exportHtml(onChange));
        clearInterval(timer);
      }
    }, 500);
  }, [ editorRef ]);

  return (
    <EmailEditor ref={editorRef} minHeight="800px" onLoad={onLoadEditor} />
  );
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants