Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Redux saga, axios and progress event

Is there clean/short/right way to using together axios promise and uploading progress event?

Suppose I have next upload function:

function upload(payload, onProgress) {
  const url = '/sources/upload';

  const data = new FormData();

  data.append('source', payload.file, payload.file.name);

  const config = {
    onUploadProgress: onProgress,
    withCredentials: true
  };

  return axios.post(url, data, config);
}

This function returned the promise.

Also I have a saga:

function* uploadSaga(action) {
  try {
    const response = yield call(upload, payload, [?? anyProgressFunction ??]);
    yield put({ type: UPLOADING_SUCCESS, payload: response });
  } catch (err) {
    yield put({ type: UPLOADING_FAIL, payload: err });
  }
}

I want to receive progress events and put it by saga. Also I want to catch success (or failed) result of the axios request. Is it possible?

Thanks.

like image 748
Dmitry Paloskin Avatar asked Nov 03 '16 13:11

Dmitry Paloskin


People also ask

Is Redux saga deprecated?

Such a powerful & elegant tool as Redux-Saga, a Redux side effect manager, is said to be deprecated, and no longer being maintained, starting from Jan 27, 2021.

What can I use instead of Redux saga?

Redux Thunk, a common alternative to Redux Saga, allows functions to be passed into the Redux store dispatch, which checks to see if it is a function or an action object, executing the function in the former case, and directly passing along the action object to the reducer in the latter case.

What is the benefit of using Redux saga?

By working with effects, Redux Saga makes sagas declarative, rather than imperative, which adds the benefit of a function that returns a simple object, which is easier to test than a function that directly makes an asynchronous call.

What is the difference between Redux and Redux saga?

Redux has a broader approval, being mentioned in 1036 company stacks & 832 developers stacks; compared to redux-saga, which is listed in 43 company stacks and 21 developer stacks.


2 Answers

So I found the answer, thanks Mateusz Burzyński for the clarification.

We need use eventChannel, but a bit canningly.

Suppose we have api function for uploading file:

function upload(payload, onProgress) {
  const url = '/sources/upload';

  const data = new FormData();

  data.append('source', payload.file, payload.file.name);

  const config = {
    onUploadProgress: onProgress,
    withCredentials: true
  };

  return axios.post(url, data, config);
}

In saga we need to create eventChannel but put emit outside.

function createUploader(payload) {

  let emit;
  const chan = eventEmitter(emitter => {

    emit = emitter;
    return () => {}; // it's necessarily. event channel should 
                     // return unsubscribe function. In our case 
                     // it's empty function
  });

  const uploadPromise = upload(payload, (event) => {
    if (event.loaded.total === 1) {
      emit(END);
    }

    emit(event.loaded.total);
  });

  return [ uploadPromise, chan ];
}

function* watchOnProgress(chan) {
  while (true) {
    const data = yield take(chan);
    yield put({ type: 'PROGRESS', payload: data });
  }
}

function* uploadSource(action) {
  const [ uploadPromise, chan ] = createUploader(action.payload);
  yield fork(watchOnProgress, chan);

  try {
    const result = yield call(() => uploadPromise);
    put({ type: 'SUCCESS', payload: result });
  } catch (err) {
    put({ type: 'ERROR', payload: err });
  }
}
like image 66
Dmitry Paloskin Avatar answered Sep 22 '22 20:09

Dmitry Paloskin


I personally found the accepted answer to be very convoluted, and I was having a hard time implementing it. Other google / SO searches all led to similar type answers. If it worked for you, great, but I found another way using an EventEmitter that I personally find much simpler.

Create an event emitter somewhere in your code:

// emitter.js

import { EventEmitter } from "eventemitter3";

export default new EventEmitter();

In your saga to make the api call, use this emitter to emit an event within the onUploadProgress callback:

// mysagas.js
import eventEmitter from '../wherever/emitter';

function upload(payload) {
  // ...

  const config = {
    onUploadProgress: (progressEvent) = {
      eventEmitter.emit(
        "UPLOAD_PROGRESS", 
        Math.floor(100 * (progressEvent.loaded / progressEvent.total))
      );
    }
  };

  return axios.post(url, data, config);
}

Then in your component that needs this upload progress number, you can listen for this event on mount:

// ProgressComponent.jsx
import eventEmitter from '../wherever/emitter';

const ProgressComponent = () => {

  const. [uploadProgress, setUploadProgress] = useState(0);

  useEffect(() => {
    eventEmitter.on(
      "UPLOAD_PROGRESS",
      percent => {
        // latest percent available here, and will fire every time its updated
        // do with it what you need, i.e. update local state, store state, etc
        setUploadProgress(percent)
      }
    );
  
    // stop listening on unmount
    return function cleanup() {
      eventEmitter.off("UPLOAD_PROGRESS")
    }
  }, [])

  return <SomeLoadingBar value={percent} />

}

This worked for me as my application was already making use of a global eventEmitter for other reasons. I found this easier to implement, maybe someone else will too.

like image 20
Seth Lutske Avatar answered Sep 25 '22 20:09

Seth Lutske