Reputation: 1018
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?
Upvotes: 12
Views: 35204
Reputation: 1491
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?
See README for more features and advanced use cases.
Upvotes: 1
Reputation: 11059
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
Upvotes: -1
Reputation: 2598
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)
Upvotes: 4
Reputation: 1098
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);
}
Upvotes: 22