Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TinyMCE React editor throws 'Cannot set property 'onload' of null'

I'm using react-tinymce with create-react-app (latest versions of both).

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)}
    />
}
like image 996
andrewhl Avatar asked Oct 29 '22 00:10

andrewhl


1 Answers

It looks like TinyMCE had a bug where it failed to remove a partially initialized editor. I think your component is being unmounted too soon after being mounted.

The bug was fixed in TinyMCE version 4.7.7: https://github.com/tinymce/tinymce/blob/6b0075fdac4d190614de7d815d067b93300f029d/changelog.txt#L140

like image 90
MarkyMarkMcD Avatar answered Nov 15 '22 06:11

MarkyMarkMcD