Reputation: 1353
I'm trying to figure out how to incorporate cancelling of multiple tasks of the same action type in redux-saga. Basically, when my component is in componentWillUnmount()
, I want to cancel all tasks that it may have started.
If I have the following action (this is greatly simplified from what I actually have in my code, but I'm trying to pare it down to the essentials):
// Action
export const loadMyData = params => {
let url = /* Create URL based on params */
return {
type: FETCH_DATA,
url,
key: /* a unique key so we can retrieve it in the state later */
}
}
And the following saga:
// Saga
export function* fetchData(action) {
try {
// 'callApi()' currently uses axios
let response = yield call(callApi, action.url);
yield put({
type: FETCH_SUCCESS,
key: action.key,
payload: response.data
});
} catch(error) {
yield put({
type: FETCH_FAILURE,
error
});
}
}
export function* watchFetchData() {
while (true) {
let action = yield take(FETCH_DATA);
let task = yield fork(fetchApi, action);
}
}
As stated above, the component may call loadMyData()
multiple times. Furthermore, there may have other components that also call loadMyData()
. So I'm trying to find a way to cancel the tasks from just the component that is in the componentWillUnmount()
state, but leave any other running tasks intact.
In the Redux Saga Cancellation documentation, their example is for a single task that expects a cancellation action after. And I can't figure out how to extend that to my use case.
Upvotes: 0
Views: 848
Reputation: 4141
What comes to my mind is the following:
In componentWillMount
you register your component by dispatching an action and storing the tasks in a reducer like so:
registerWatchFetchData = (componentKey) => {
return {
type: "REGISTER_WATCH_FETCH_DATA",
payload: { componentKey }
}
}
reducer:
// ...
case "REGISTER_WATCH_FETCH_DATA":
return {...state, tasks: {...state.tasks, [action.payload.componentKey]: []}}
Then inside function* watchFetchData()
you store the new task in the reducer for the corresponding component keying on componentKey which you provide in the payload
:
export function* watchFetchData() {
while (true) {
let action = yield take(FETCH_DATA);
let task = yield fork(fetchApi, action);
yield put({ type: "STORE_TASK", payload: { componentKey: action.payload.componentKey, task } })
}
}
then add to the reducer
// ...
case "STORE_TASK":
return {...state, tasks: {...state.tasks, [action.payload.componentKey]: [...state.tasks[action.payload.componentKey], action.payload.task]}}
And in componentWillUnmount
you dispatch another action to tell a saga to pull all tasks for the componentKey, iterate through them and cancel them all like so:
function* watchUnregisterComponent(){
while(true){
const action = yield take("UNREGISTER_WATCH_FETCH_DATA")
const componentTasks = yield select(state => state.myReducer.tasks[action.payload.componentKey])
componentTasks.forEach((t) => {
yield cancel(t)
})
// dispatch another action to delete them from the reducer
}
}
Upvotes: 2