Search code examples
javascriptreactjsreduxsummernoteredux-thunk

Redux thunk not resolving before next action + then is not a function or dispatch undefined


Been working on a react/redux app, and I've been pounding my head against the wall on this action.

Problem - I'm updating files using a wzywyg editor. When I upload a photo, the application tries to insert the image before the action has finished processing the image. Thus, leading to image url undefined. When I upload a second image, the first image is inserted and so on.

It's clear that the thunk is not resolving before the next action is called.

More Details

  • There is a text editor in my application (summernote-react). When a user uploads an image, onImageUpload fires and triggers handleImageUpload.
  • From there, I dispatch an action via mapStatetoProps, and the image is processed for upload.
  • After the image is processed, it should return to the editor and embed the image path into the editor for preview.

Right now, I'm getting the same pattern of errors around dispatch being undefined, and then not being a function. What am I missing?

addUploadToDocument(...).then is not a function
Uncaught TypeError: Cannot read property 'then' of undefined

I can confirm the following: - Redux-thunk is setup correctly. It's working and firing in our production version. - The action is being called from my component. Just not sure if dispatch is. I can successfully upload the image, but callback is initiated before action is resolved.

Here's my actions code:

// actions.js
// thunk
export function loadImage(document, file) {
  return (dispatch, getState) => {
   return  dispatch(addUploadToDocument(document, file))
      .then(() => {
        console.log('Thunk is loaded.. chyeah.');
        var uploads = this.props.uploads
        var image = uploads[uploads.length - 1]    
        ReactSummernote.insertImage(image.previewURL, $image => {
          $image.css("width", Math.floor($image.width() / 2));
          $image.attr("alt", image.name);
        });
      });
  }
}
//image processing action
export function addUploadToDocument(document, file) {    
  return (dispatch, getState) => {
    //const position = getState().bodyEditorSelection.index
    const base64Reader = new FileReader()    
    base64Reader.addEventListener('load', function() {
      const base64 = base64Reader.result.replace(/data:.*?base64,/, '')
      const key = Math.random().toString(36).substring(7)    
      const destination_path = `/uploads/${document.name}/${key}-${file.name}`
      return dispatch({
        type: DOCUMENT_ADD_UPLOAD,
        payload: {
          path: destination_path,
          type: file.type,
          base64: base64,
          name: document.name,
          previewURL: window.URL.createObjectURL(file),
          //position: position
        }
      })
    })
    base64Reader.readAsDataURL(file)
  }
}

And here is my component.

  handleImageUpload (files, editor, welEditable) {
    var file = files[files.length -1];
    this.props.loadImage(this.props.document, file)
  }
  render() {
    this.syncBodyEditorState()
    this.state = this.state || {}
    return (
        <ReactSummernote
        value={this.props.body}
        options={{
          height: 750,
          dialogsInBody: true,
          toolbar: [
            ["style", ["style"]],
            ["font", ["bold", "underline", "clear"]],
            ["fontname", ["fontname"]],
            ["para", ["ul", "ol", "paragraph"]],
            ["table", ["table"]],
            ["insert", ["link", "picture", "video"]],
            ["view", ["codeview"]]
          ]
        }}
        onImageUpload={this.handleImageUpload}
        onChange={this.handleChange}
      />
    )
  }
}

function mapStateToProperties(state) {
  const currentDocument = currentDocumentSelector(state).currentDocument    
  return {
    document: currentDocument,
    bodyEditorSelection: state.bodyEditorSelection,
    body: currentDocument.content.body,
    uploads: currentDocument.content.uploads
  }
}    
export default connect(mapStateToProperties, {
  SummernoteEditor,
  updateDocumentBody,
  updateBodyEditorSelection,
  addUploadToDocument,
  loadImage
})(SummernoteEditor)

Am I missing something trivial? I've looked at dozens of examples, thunk has me stumped!

Thanks in advance for the help.


Solution

  • Right now your addUploadToDocument function doesn't return a promise, despite this you .then your dispatch method (and it's just a thunk that returns a plain object). If you need continuation, you could wrap the contents of addUploadToDocument function in a promise then it will be thenable. Here's a bit different approach with some refractoring to get you started (assuming 'load' event can only happen once):

    export function loadImage(document, file) {
      return (dispatch, getState) => {
        const base64Reader = new FileReader();
    
        const promise = new Promise((resolve, reject)=> {
          base64Reader.addEventListener('load', resolve)
        })
            .then(()=> {
              const base64 = base64Reader.result.replace(/data:.*?base64,/, '')
              const key = Math.random().toString(36).substring(7)
              const destination_path = `/uploads/${document.name}/${key}-${file.name}`
              dispatch({
                type: DOCUMENT_ADD_UPLOAD,
                payload: {
                  path: destination_path,
                  type: file.type,
                  base64: base64,
                  name: document.name,
                  previewURL: window.URL.createObjectURL(file),
                  //position: position
                }
              })
            })
            .then(()=> {
              console.log('Thunk is loaded.. chyeah.');
              var uploads = this.props.uploads
              var image = uploads[uploads.length - 1]
              ReactSummernote.insertImage(image.previewURL, $image => {
                $image.css("width", Math.floor($image.width() / 2));
                $image.attr("alt", image.name);
              });
            })
        base64Reader.readAsDataURL(file);
        return promise;
       }
    }