instructure-react / react-tinymce

React TinyMCE component
181 stars 115 forks source link

uncaught at handleCall TypeError: Cannot set property 'onload' of null #78

Open andrewhl opened 6 years ago

andrewhl commented 6 years ago

I'm using react-tinymce with create-react-app.

I'm getting the following error when the editor component mounts:

uncaught at handleCall TypeError: Cannot set property 'onload' of null
    at p.unbindAllNativeEvents (https://cloud.tinymce.com/stable/tinymce.min.js?apiKey=s057mcau7lzqdzu5tu3vx99qiek91pkj0od7u00dbw6kuk65:18:2293)
    at p.remove (https://cloud.tinymce.com/stable/tinymce.min.js?apiKey=s057mcau7lzqdzu5tu3vx99qiek91pkj0od7u00dbw6kuk65:20:9142)
    at Object.execCommand (https://cloud.tinymce.com/stable/tinymce.min.js?apiKey=s057mcau7lzqdzu5tu3vx99qiek91pkj0od7u00dbw6kuk65:20:19531)
    at Object._remove (http://localhost:3000/static/js/bundle.js:127305:27)
    at Object.componentWillUnmount (http://localhost:3000/static/js/bundle.js:127245:10)

The section in question is in tinymce.js:

            unbindAllNativeEvents: function() {
                var a, b = this;
                if (b.delegates) {
                    for (a in b.delegates)
                        b.dom.unbind(d(b, a), a, b.delegates[a]);
                    delete b.delegates
                }
                b.inline || (b.getBody().onload = null,
                b.dom.unbind(b.getWin()),
                b.dom.unbind(b.getDoc())),
                b.dom.unbind(b.getBody()),
                b.dom.unbind(b.getContainer())
            }

b.getBody() is returning null. This only happens intermittently. Sometimes the editor loads correctly. I should note that I am integrating the editor into react-redux-form, as a custom Control component.

    render(): React.Element<*> {
        return (
            <div>
                <input type="file" id="image-upload-tinymce" name="single-image" style={{ display: "none" }}
                       accept="image/png, image/gif, image/jpeg, image/jpg, image/svg" />
                <UIForm
                    as={Form}
                    model={this.props.formModel}
                    onSubmit={(foo) => this.handleSubmit(foo)}
                >
                    <Control
                        model={this.props.fieldModel}
                        component={TinyMCECustom}
                        mapProps={{
                            content: (props) => props.viewValue,
                        }}
                        updateContent={this.props.updateContent}
                        validators={{
                            required: val => val && val.length > 10
                        }}
                    />
                </UIForm>
            </div>
        );
    }

I am initializing the value of the form from a reducer that react-redux-form connects to my redux state. The reducer has the following structure:

export default function reducer(state = initialState, action) {
    switch (action.type) {
    case articleModule.GET_SUCCESS:
    case articleModule.SAVE_SUCCESS: {
        const article = action.payload.data;
        return {
            ...state,
            description: article.description || '',
            uuid: article.uuid,
        }
    }
    default:
        return state;
    }
}

Sometimes the editor loads just fine, prepopulating with the description of article from the reducer. This suggests to me that the issue is asynchronous, and is happening when the TinyMCE component attempts to mount before it has received the data from the react-redux-form reducer. I am setting default values in the reducer, however, so I'm not sure if something else is causing this issue.

This is my implementation of TinyMCECustom:

const filePickerCallback = (callback, value, meta) => {
    if (meta.filetype !== 'image') {
        return;
    }

    let input = document.getElementById('image-upload-tinymce');
    input.click();

    input.onchange = () => {
        let file = input.files[0];
        let reader = new FileReader();

        reader.onload = (e) => {
            let img = new Image();
            img.src = reader.result;

            callback(e.target.result, {
                alt: file.name
            });
        };

        reader.readAsDataURL(file);
    };
}

const handleEditorChange = (e, props) => {
    props.updateContent(e.target.getContent());
};

const handleOnBlur = (e, props) => {
    props.updateContent(e.target.getContent());
}

const handleOnKeyup = (e, props) => {
    const updateContent = _.debounce(() => props.updateContent(e.target.innerHTML), 300);
    updateContent();
}

const TinyMCECustom = (props) => {
    return <TinyMCE
        content={props.content}
        config={{
            plugins: ['advlist autolink lists link image charmap print preview hr anchor pagebreak',
                'searchreplace wordcount visualblocks visualchars code fullscreen',
                'insertdatetime media nonbreaking save table contextmenu directionality',
                'emoticons template paste textcolor colorpicker textpattern imagetools codesample toc help'],
            toolbar1: 'undo redo | insert | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image',
            toolbar2: 'print preview media | forecolor backcolor emoticons | codesample help',
            image_advtab: true,
            file_browser_callback_types: 'image',
            file_picker_callback: filePickerCallback,
            branding: false,
            height: 400,
        }}
        {...props}
        onChange={(e) => handleEditorChange(e, props)}
        onBlur={(e) => handleOnBlur(e, props)}
        onKeyup={(e) => handleOnKeyup(e, props)}
    />
}
codejunkyard commented 6 years ago

I have a similar issue working with react-tinymce (0.6.0) and redux-form (7.1.1) while loading my data asynchronously. @andrewhl, have you found a work-around in the mean-time?

Just upgraded to react-tinymce 0.7.0 and I get the same error.

andrewhl commented 6 years ago

@gvautour Unfortunately, I have not. The only solution that seems to accomplish anything is setting a manual setTimeout of 2-3 seconds before rendering the editor. This is, obviously, not ideal. Please let me know if you find anything. I'll post this to Stack Overflow and see if I get any hits.

andrewhl commented 6 years ago

@gvautour Here's my StackOverflow post

andrewhl commented 6 years ago

@gvautour I found a viable workaround. The one I posted above doesn't actually work. Upgrade to React 16, and use an error boundary component. See this article.

You wrap your TinyMCE component in <PotentialError></PotentialError>, and React will re-render the component if it throws the onload error. It'll still error in the console, but the wysiwyg editor will rerender and be usable.

MarkyMarkMcDonald commented 5 years ago

For others stumbling across this: It looks like this is fixed in version 4.7.7 of TinyMCE: https://github.com/tinymce/tinymce/blob/6b0075fdac4d190614de7d815d067b93300f029d/changelog.txt#L140