How to "yield put" in redux-saga within a callback?
Asked Answered
E

4

32

Because "yield"-statement isn't allowed within a callback, how can i use the "put" feature of redux-saga within a callback?

I'd like to have the following callback:

function onDownloadFileProgress(progress) {
  yield put({type: ACTIONS.S_PROGRESS, progress})
}

This isn't working and ends up in "unexpected token", because yield is not allowed in a plain function. Otherwise i can't pass a callback as a "function *", that would allow yield. ES6 seems broken here.

I've read that redux-saga provides some features called "channels", but to be honest, i didn't get it. I've read several times about these channels and example code, but in all examples they've solved very difficult and different problems, not my simple case and at the end of the day i've got up there.

Can someone tell me a solution how to deal with this issue?

The whole context:

function onDownloadFileProgress(progress) {
  yield put({type: ACTIONS.S_PROGRESS, progress})
}

export function * loadFile(id) {
  let url = `media/files/${id}`;

  const tempFilename = RNFS.CachesDirectoryPath + '/' + id;

  const download = RNFS.downloadFile( {
    fromUrl: url,          
    toFile: tempFilename,  
    background: false,
    progressDivider: 10,
    progress: onDownloadFileProgress,
  })

  yield download.promise;

}
Evy answered 26/3, 2017 at 17:4 Comment(2)
It's not allowed <-> you cannotTowner
what do you want to say with that? makes it a difference?Evy
R
61

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.

Replevy answered 2/4, 2017 at 8:29 Comment(5)
Works great! Thank you very much!Evy
This seems like a solution for my similar problem, but I'm getting channel buffer overflows. How did you wire up the watchDownloadFileChannel to the sagaMiddleware?Dispersion
Thank you! This helped me get socket.io working with redux-saga in a much cleaner wayBiforate
How can you wire the watchDownloadFileChannel to the saga middleware?Keeter
Ok, for who is wondering how to wire the watchDownloadChannel. you can simply export this code takeEvery(channel, watchDownloadChannel) and import it into the rootSagaKeeter
S
2

That's the most simple way:

import store from './store' // import redux store
store.dispatch(your action creator here)
Strep answered 9/3, 2021 at 9:37 Comment(0)
B
1

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 component
handleFileUpload(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

Bedmate answered 7/4, 2017 at 14:59 Comment(0)
D
1

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);
Dupondius answered 16/5, 2017 at 17:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.