How to "yield put" in redux-saga within a callback?
One possible solution, as you already mentioned, is to use channels
. Here is an example that should work in your case:
import { channel } from 'redux-saga'import { put, take } from 'redux-saga/effects'const downloadFileChannel = channel()export function* loadFile(id) { ... const download = RNFS.downloadFile({ ... // push `S_PROGRESS` action into channel on each progress event progress: (progress) => downloadFileChannel.put({ type: ACTIONS.S_PROGRESS, progress, }), }) ...}export function* watchDownloadFileChannel() { while (true) { const action = yield take(downloadFileChannel) yield put(action) }}
The idea here is that we will push a S_PROGRESS
action on the channel for each progress event that is emitted from RNFS.downloadFile
.
We also have to start another saga function that is listening to each pushed action in a while
loop (watchDownloadFileChannel
). Everytime an action has been taken from the channel, we use the normal yield put
to tell redux-saga
that this action should be dispatched.
I hope this answer will help you.
I got myself into a similar situation this week.
My solution was to call a dispatch inside the callback and pass the result.
I was handling file uploads so wanted to do a readAsArrayBuffer()
call, initially in my saga something like this:
function* uploadImageAttempt(action) { const reader = new FileReader(); reader.addEventListener('loadend', (e) => { const loadedImage = reader.result; yield put(Actions.uploadImage(loadedImage)); // this errors, yield is not allowed }); reader.readAsArrayBuffer(this.refs[fieldName].files[0]);}
How I got round this was by doing the readAsArrayBuffer()
in my component, then call a connected dispatch function:
// in my file-uploader componenthandleFileUpload(e, fieldName) { e.preventDefault(); const reader = new FileReader(); reader.addEventListener('loadend', (e) => { const loadedImage = reader.result; this.props.uploadFile( this.constructDataObject(), this.refs[fieldName].files[0], loadedImage ); }); reader.readAsArrayBuffer(this.refs[fieldName].files[0]);}...const mapDispatchToProps = (dispatch) => { return { uploadFile: (data, file, loadedImage) => { dispatch(Actions.uploadFile(data, file, loadedImage)) } }}
Hope that helps
Beside using channel
as @Alex suggest, one might also consider using call
from 'redux-saga/effects'
. The call
effect take a function or Promise
.
import { call } from 'redux-saga/effects';// ...yield call(download.promise);