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

side effects of onLoad implementation

Open HyperMaxime opened this issue 7 years ago • 16 comments

The passed wrapper prop onLoad function gets called in direct response (synchronous) to the onLoad callback of the script being loaded. This does not always guarantee that the underlying component is already loaded by then.

For example the editor can be in specific route of a react application, and load the unlayer script the first time the user gets there. If the user then navigates away from that route and comes back, the script will be loaded from the cache, therefore immediately kicking in the onLoad callback. In the demo, the onLoad function will execute this.editor.loadDesign(sample) and in my particular case add event listeners. It then blows up saying this.editor is undefined.

HyperMaxime avatar Jun 06 '18 12:06 HyperMaxime

@HyperMaxime can you share a functional code sample of your particular scenario? If not, then any snippets would help too.

umairsiddique avatar Jun 09 '18 01:06 umairsiddique

@umairsiddique I also get the same issue from time to time.

My onLoad function:

  onLoad = () => {
    this.editor.loadDesign(this.props.firm_prefs[1].email_templates[this.state.selectedTemplate]);
    this.editor.setMergeTags([ {name: "First Name", value: "{*|first_name|*}"}, {name: "Last Name", value: "{*|last_name|*}"}, {name: "Invite Link", value: "{*|invite_link|*}"}]);
  }

Where I use the onLoad:

              <EmailEditor
              onLoad={this.onLoad} 
              ref={editor => this.editor = editor}
              />
            </div>

Error it spits out:

image

stroypet avatar Jun 10 '18 18:06 stroypet

@umairsiddique I created a CodeSandbox that illustrates the issue: https://codesandbox.io/s/5kz6nzjq9p

By showing and hiding the editor, you'll see in the log that the editor is undefined when the onLoad callback function executes.

Also notice that sometimes it is actually available at the start, depending on the browser having cached the unlayer script.

HyperMaxime avatar Jun 11 '18 17:06 HyperMaxime

@umairsiddique @HyperMaxime sorry to be a pest, but did either of you ever find a solution or work around to this issue?

Cheers.

stroypet avatar Jun 18 '18 13:06 stroypet

@stroypet until we get a better solution/fix we are just pointing to the global variable unlayer.

coxom avatar Jun 18 '18 16:06 coxom

@coxom Thanks for the reply, don't suppose I could bother you for a code snippet of what that looks like? My .js skills aren't the greatest :).

stroypet avatar Jun 22 '18 16:06 stroypet

@stroypet The usage example would become something like this:

import React, { Component } from 'react'
import { render } from 'react-dom'
import EmailEditor from 'react-email-editor'

class App extends Component {
  render() {
    return <div>
      <h1>react-email-editor Demo</h1>
      <div>
        <button onClick={this.exportHtml}>Export HTML</button>
      </div>
      <EmailEditor />
    </div>
  }

  exportHtml = () => {
    unlayer.exportHtml(data => {
      const { design, html } = data
      console.log('exportHtml', html)
    })
  }
}

render(<App />, document.getElementById('app'))

HyperMaxime avatar Jul 02 '18 09:07 HyperMaxime

Solution with the global variable works. Just use window.unlayer. Was trying to fix it half a day, thank you all. It is not good to use global variables in React but who cares, IT WORKS! ))

car1sberg avatar Jul 18 '18 14:07 car1sberg

The key fix is to check if this.editor && window.unlayer != undefined and by conditionally setting the onLoad prop. In my particular use case, I am using react-router-dom and an api call that Redux dispatches when the componentDidMount. I figured out this fix (fyi this.props.match is a prop passed in from react-router-dom so ignore it if your component is not using it):

// Function outside render but inside class MyClassName extends Component { }
 loadDesign = design => this.editor.loadDesign(design)

 render() {
// Check if Redux updated the state from mapStateToProps
const design = this.state.HtmlDocument.hasOwnProperty('design') ? JSON.parse(this.state.HtmlDocument.design) : null

// True if there are paramaters in the url, redux updated the state, and if the editor has loaded into memory
 const isEditingDesign = this.props.match && design && this.editor && window.unlayer

return (
<EmailEditor minHeight="85vh" ref={editor => this.editor = editor} style={styles} onLoad={isEditingDesign ? this.loadDesign(design) : null}/>
)
}
export default withRouter(reduxConnect(mapStateToProps, mapDispatchToProps)(MyClassName))

If anyone needs additional clarity, please respond to this comment. I am willing to share more code.

nathanhfoster avatar Oct 07 '18 01:10 nathanhfoster

@strap8, I did not test your code in practise so I might be wrong, but at first sight I think your solution misses the point of this issue. Under certain conditions, this.editor will not yet be available when the onLoad callback gets called, therefore throwing an exception if any method of it is being executed. Your solution simply doesn't run any callback in that scenario, which will indeed not throw any exception, but will also eventually not run code that is expected to run at the moment the editor is loaded.

HyperMaxime avatar Oct 11 '18 14:10 HyperMaxime

I'm using a workaround to fix this. I facing this problem when the route change, using react-router-dom.

So, to "solve" this, I'm using 2 flags: isEditorLoaded and isComponentMounted

Initiate them in the constructor: constructor(props) { super(props); this.editor = null; this.isEditorLoaded = false; this.isComponentMounted = false; }

Implement the hook: componentDidMount() { this.isComponentMounted = true; this.loadTemplate(); }

Implement the onLoad: onLoad = () => { this.isEditorLoaded = true; this.loadTemplate(); }

Then a function to be called and check if we are able to use the editor: loadTemplate = () => { if (!this.isEditorLoaded || !this.isComponentMounted) return; this.editor.loadDesign(this.state.jsonTemplate) }

The render: <EmailEditor ref={editor => this.editor = editor} onLoad={this.onLoad} />

I hope this can help.

mvcarvalho avatar Apr 11 '19 13:04 mvcarvalho

This solution works for me https://github.com/unlayer/react-email-editor/issues/7#issuecomment-348227970

andrewstudionone avatar May 14 '19 01:05 andrewstudionone

@mvcarvalho This worked for me when trying to use setMergeTags. It feels like the best workaround so far. Have you managed to get it working in a different way since your post?

ZeroDarkThirty avatar Dec 11 '19 00:12 ZeroDarkThirty

@mvcarvalho Works for me, thank you!

Crimiro avatar Jun 09 '20 17:06 Crimiro

in case it helps anyone, I have posted up a working solution at https://github.com/unlayer/react-email-editor/issues/100#issuecomment-644694574

andrewlorenz avatar Jun 16 '20 11:06 andrewlorenz

Sometimes the email editor shows up but most of the time on refreshing/reconnecting I get these errors in the browser console. The same thing happens in the case of "save Design" and "load design" both

  1. ERROR: (Cannot read property save Design/ load Design of Undefined)
  2. ERROR: (Prop id did not match. Server: "editor-2" Client: "editor-1")

sometimes even with ERROR2 things work fine. WHAT SHOULD I DO......? image

image

igoswamik avatar Apr 14 '21 07:04 igoswamik