Promises in redux-saga

I found the same question here, but without a proper answer I am looking for.

I am developing a simple application with CRUD operations. On the edit page, after the component gets mounted (componentDidMount()), the app dispatches an action to retrieve a specific post details:

dispatch({ type: FETCH_POST, id: 'post-id' })

I am using redux-saga and want the above call to return a Promise so that I can access the API response.

Right now, without a callback/Promise, I ended up with defining a new state in store (like post_edited) and connect/map it to props in the component for edit page.

What would be the best possible way to deal with this kind of situation?

Answers:

Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.

Method 1

Could you please provide more information about your issue? I’m not sure if I understand your issue properly, but the common practice is:

API.js

function apiCallToFetchPost(id) {
  return Promise.resolve({name: 'Test});
}

postSaga.js
function* fetchPostSaga({id}) {
  try {
    const request = yield call(apiCallToFetchPost, id);
    // -> in post reducer we will save the fetched data for showing them later 
    yield put({type: FETCH_POST_SUCCESS, payload: request}); 
  } catch (error) {
    yield put({type: FETCH_POST_SUCCESS_FAILURE, error})
  }
}

export function* onBootstrap() {
  yield takeLatest(FETCH_POST, fetchPostSaga);
}

Method 2

There’s a package that does exactly what the OP requested, i.e. arranges that dispatch() can return a promise: @adobe/redux-saga-promise
Using it, you define a “promise action” creator via:

import { createPromiseAction } from '@adobe/redux-saga-promise'

export const fetchPostAction = createPromiseAction('FETCH_POST')

The dispatch() of a “promise action” will return a promise:

await dispatch(fetchPostAction({ id: 'post-id' }))

The saga might look like:

import { call, takeEvery }        from 'redux-saga/effects'
import { implementPromiseAction } from '@adobe/redux-saga-promise'

import { fetchPostAction } from './actions'

function * fetchPostSaga(action) {
  yield call(implementPromiseAction, action, function * () {
    const { id } = action.payload
    return yield call(apiCallToFetchPost, id)
  })
}

export function * rootSaga() {
  yield takeEvery(fetchPostAction, fetchPostSaga);
}

It will resolve the promise with the value returned by apiCallToFetchPost or reject if apiCallToFetchPost throws an error. It also dispatches secondary actions with the resolution/rejection that you can access in a reducer. The package provides middleware you have to install to make it work.

(Disclaimer, I’m the author)

Method 3

I am the developer of @teroneko/redux-saga-promise. It was initially forked from @adobe/redux-saga-promise but now it has been completelly revamped to use createAction from @reduxjs/toolkit to support TypeScript.

To keep in touch with the example of @ronen, here the TypeScript equivalent.

Create promise action (creator):

import { promiseActionFactory } from '@teroneko/redux-saga-promise'
 
export const fetchPostAction = promiseActionFactory<void>().create<{ id: string }>('FETCH_POST')

To dispatch a promise action (from creator):
// promiseMiddleware is required and must be placed before sagaMiddleware!
const store = createStore(rootReducer, {}, compose(applyMiddleware(promiseMiddleware, sagaMiddleware)))
await store.dispatch(fetchPostAction({ id: 'post-id' }))

To resolve/reject the promise action (from saga):
import { call, takeEvery }        from 'redux-saga/effects'
import { implementPromiseAction } from '@teroneko/redux-saga-promise'

import { fetchPostAction } from './actions'

function * fetchPostSaga(action: typeof fetchPostAction.types.triggerAction) {
  yield call(implementPromiseAction, action, function * () {
    const { id } = action.payload
    return yield call(apiCallToFetchPost, id)
  })
  // or for better TypeScript-support
  yield call(fetchPostAction.sagas.implement, action, function * () {
    const { id } = action.payload
    return yield call(apiCallToFetchPost, id)
  })
}

export function * rootSaga() {
  yield takeEvery(fetchPostAction, fetchPostSaga);
}

So what’s going on?
  1. promise action (creator) gets created
  2. promise action (from creator) gets created and
  3. dispatched to store.
  4. Then the promise action gets converted to a awaitable promise action where its deferred version is saved into the meta property. The action is immediatelly returned and
  5. passed to saga middleware.
  6. The now awaitable promise action is qualified to be used in implementPromiseAction that nothing else does than resolving or rejecting the deferred promise that is saved inside the meta property of the awaitable promise action.

See README for more features and advanced use cases.

Method 4

Another solution

onSubmit: (values) => {
  return new Promise((resolve, reject) => {
    dispatch(someActionCreator({ values, resolve, reject }))
  });
}

In saga:
function* saga() {
  while (true) {
    const { payload: { values, resolve, reject } } = yield take(TYPE)
    // use resolve() or reject() here
  }
}

Reference: https://github.com/redux-saga/redux-saga/issues/161#issuecomment-191312502


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x