无法从操作创建者中分派异步操作

Trouble dispatching async action from within actions creator

我试图在图像作为表单的一部分上传时启动加载微调器,并在对图像的引用保存在 Firebase 中时停止它。

我的 Actions.js 文件中的此函数 returns 来自给定表单字段的输入:

export const formUpdate = ({ prop, value }) => { alert('update') return { type: FORM_UPDATE, payload: { prop, value } }; };

使用 Connect,我使用 formUpdate 为我的 Form.js 组件中的不同表单字段存储值 - 这工作正常。

我在 Actions.js 中使用一个单独的函数来处理图像上传,上传后,我调用这个函数在 Firebase 中保存一个引用:

export const saveImageReference = (downloadUrl, sessionId) => { const { currentUser } = firebase.auth(); firebase .database() .ref(`users/${currentUser.uid}/images`) .push({ imageId: sessionId, imageUrl: downloadUrl }) .then(formUpdate({ prop: 'loading', value: false })); };

我正在尝试让我的表单在上传过程中显示加载微调器。为此,我在 saveImageReference 末尾使用 formUpdate 来发送 loading 道具。但是,这是行不通的。

formUpdate 作为 .then() 块的一部分执行 - 我看到了确认这一点的警报 - 但没有数据进入表单组件。

我也试过使用不同的 prop(例如 'name')来查看它是否更新了表单字段,但没有任何反应。

redux-thunk 工作正常 - 我使用类似的方法在我的登录表单中显示一个旋转器 - 但这个动作似乎不想玩球。

如果有帮助,这里是来自我的表单组件的mapStateToProps

  const { name, location, loading } = state.testForm;
  return {
    loading,
    name,
    activity
  };
};

export default connect(
  mapStateToProps,
  { formUpdate, uploadImage }
)(Form);

更新

这是基于 azundo 的回答的 uploadImage 代码。这不执行:

export const uploadImage = (
  uri,
  mime = 'application/octet-stream'
) => dispatch => {
  const { Blob } = RNFetchBlob.polyfill;
  window.XMLHttpRequest = RNFetchBlob.polyfill.XMLHttpRequest;
  window.Blob = Blob;

  const { currentUser } = firebase.auth();

  console.log('Starting upload action...');
  return new Promise((resolve, reject) => {
    console.log('in promise 1');
    const uploadUri = Platform.OS === 'ios' ? uri.replace('file://', '') : uri;
    const sessionId = new Date().getTime();
    // create a reference in firebase storage for the file
    let uploadBlob = null;
    const imageRef = firebase
      .storage()
      .ref(`user/${currentUser.uid}/images`)
      .child(`image_${sessionId}`);

    // encode data with base64 before upload
    RNFetchBlob.fs
      .readFile(uploadUri, 'base64')
      .then(data => {
        console.log('Encoding image...');
        return RNFetchBlob.polyfill.Blob.build(data, {
          type: `${mime};BASE64`
        });
      })
      // put blob into storage reference
      .then(blob => {
        uploadBlob = blob;
        console.log('uploading...');
        return imageRef.put(blob, { contentType: mime });
      })
      .then(() => {
        console.log('Getting download URL...');
        uploadBlob.close();
        return imageRef.getDownloadURL();
      })
      .then(url => {
        console.log('Saving reference...');
        // setLoading();
        resolve(url);
        saveImageReference(url, sessionId);
      })
      .then(() => {
        dispatch(formUpdate({ prop: 'loading', value: false }));
      })
      .catch(error => {
        reject(error);
      });
  });
};

根据您的描述,saveImageReference 中的 formUpdate 调用实际上并没有调度您的操作,它只是调用了 formUpdate 函数,它只是 returns 普通动作对象。您需要找到一些方法来实际调度该操作。

假设 uploadImage 是一个 redux-thunk 动作,我建议将动作调度的知识保留在 saveImageReference 函数之外,而是从 uploadImage 调度:

export const saveImageReference = (downloadUrl, sessionId) => {
  const { currentUser } = firebase.auth();
  // note that we are now returning the firebase promise here
  return firebase
    .database()
    .ref(`users/${currentUser.uid}/images`)
    .push({
      imageId: sessionId,
      imageUrl: downloadUrl
    });
};

const uploadImage = (arg1, arg2) => dispatch => {
  // other upload code here prior to calling the firebase function...
  saveImageReference(downloadUrl, sessionId).then(() => {
    dispatch(formUpdate({prop: 'loading', value: false}));
  });

})

如果您在等待异步操作时尝试呈现加载器。你可以使用悬念。

这将是一个更好的选择。

const OtherComponent = React.lazy(() => import('./OtherComponent'));

function MyComponent() {
  return (
    <React.Suspense fallback={<Spinner />}>
      <div>
        <OtherComponent />
      </div>
    </React.Suspense>
  );
}

经过大量调查,我解决了这个问题。我将 saveImageReference() 从 Actions.js 移动到我的组件:

addImage = () => {
    ImagePicker.showImagePicker(response => {
      if (!response.didCancel) {
        // shows modal with a form for user to select an image and add metadata
        this.setState({ showModal: true });

        // set the loading spinner in the form to show image is uploading
        this.props.formUpdate({ prop: 'loading', value: true });

        // takes the selected image and invokes uploadImage
        uploadImage(response.uri).then(url => {

        // once image is uploaded, generate sessionId in the component, and invoke saveImageReference
          const sessionId = new Date().getTime();
          this.props.saveImageReference(url, sessionId);
        });
      }
    });
  };

uploadImage() 操作创建者使用成功上传图片的 URL 进行解析,saveImageReference() 使用该图片创建引用。

保存该引用后,saveImageReference() 会调度一个专门的操作来将加载设置为 false。这是Actions.js的内容:

export const uploadImage = (uri, mime = 'application/octet-stream') => {
  const { Blob } = RNFetchBlob.polyfill;
  window.XMLHttpRequest = RNFetchBlob.polyfill.XMLHttpRequest;
  window.Blob = Blob;

  const { currentUser } = firebase.auth();

  console.log('Starting upload action...');
  return new Promise((resolve, reject) => {
    console.log('in promise');
    const uploadUri = Platform.OS === 'ios' ? uri.replace('file://', '') : uri;
    const sessionId = new Date().getTime();
    // create a reference in firebase storage for the file
    let uploadBlob = null;
    const imageRef = firebase
      .storage()
      .ref(`user/${currentUser.uid}/images`)
      .child(`image_${sessionId}`);

    // encode data with base64 before upload
    RNFetchBlob.fs
      .readFile(uploadUri, 'base64')
      .then(data => {
        console.log('Encoding image...');
        return RNFetchBlob.polyfill.Blob.build(data, {
          type: `${mime};BASE64`
        });
      })
      // put blob into storage reference
      .then(blob => {
        uploadBlob = blob;
        console.log('uploading...');
        return imageRef.put(blob, { contentType: mime });
      })
      .then(() => {
        console.log('Getting download URL...');
        uploadBlob.close();
        return imageRef.getDownloadURL();
      })
      .then(url => {
        resolve(url, sessionId);
      })
      .catch(error => {
        reject(error);
      });
  });
};

export const saveImageReference = (downloadUrl, sessionId) => {
  const { currentUser } = firebase.auth();
  console.log('Saving reference!');
  return dispatch => {
    firebase
      .database()
      .ref(`users/${currentUser.uid}/images`)
      .push({
        imageId: sessionId,
        imageUrl: downloadUrl
      })
      .then(ref => {
        console.log(ref.key);
        dispatch(imageUploadComplete());
      });
  };
};

const imageUploadComplete = () => {
  return dispatch => {
    return dispatch({
      type: IMAGE_CREATE,
      payload: false
    });
  };
};

无论我尝试什么,我都无法从 saveImageReference() 中发送另一个操作 - 引入 return dispatch 会冻结流程,没有它我会得到 dispatch is not defined

在组件级别调用它,使用 this.props.saveImageReference() 解决了问题。