How to Cancel a Redux Saga action?

I spent some time figuring out the answer to this question. This isn't a big deal if you are not really concerned about memory leaks or unnecessary operations (server request etc.) in your React Native (or React or where ever you are using redux-saga) application. Anyway, if you are here you care so let's dig into my specific problem and how to solve it.

Let's say you are using redux-saga to handle all of your API requests and every time you need some data you dispatch an action with request parameters and callbacks to handle the returned data, your action goes and finds the saga from the watchers you wrote and uses some generic request function to get the data. After getting your response you put your data in the hands of the callback to carry back to your page (you can also use a reducer to store your data but in this case let's say it doesn't make much sense to use). Well maybe in most cases the page will be there waiting to update the state with your data but sometimes the page closes and you see a warning saying something like don't try to update a state in an unmounted component.

If you think about it, you don't need to update that state because there is no page to show that state anymore. So what makes the most sense is canceling the action/request when the page is no longer there (We are just gonna cancel the saga in this post but you can also connect cancellation to the request so it'll automatically get canceled).

Let's start from the top, we have an action to get things started;

const getDataFromServer = createAction(
    'GET_DATA_FROM_SERVER',
    (payload: GetDataFromServerParams) => ({ payload }),
);

interface GetDataFromServerParams {
    params:{...someParams},
    onSuccess:(data:any[]),
    onError:(message:string),
}

I am using redux-smart-actions library to simplify actions here. (Basically, it puts keys and the action together).

const cancelGettingDataFromServer = createAction(
    'CANCEL_GETTING_DATA_FROM_SERVER',
);

We also need an action for cancellation.

dispatch(
    getDataFromServer({
        params: /*...*/,
        onSuccess: (data:any[]) => {
           setList(data);
        },
        onError:(message)=>/*...*/,
    }),
);

We are gonna use our action to get the data from server and update our state.

function* getDataFromServerSaga( { payload }: ActionPayload<GetDataFromServerParams> ) {
    const {params, onSuccess, onError} = payload;
    try {
            /*...*/
            onSuccess(response.data);
    } catch (e) {
            /*...*/
    } finally {
            const isCancelled: boolean = yield cancelled();
            if (isCancelled) /* if you need something to do in case of cancellation*/
    }
}

Here we are just getting our data from server and returning it to our page.

function* getDataFromServerWatcher() {
    yield takeLatest(getDataFromServer.toString(), function* (args) {
    yield race({
            saga: call(getDataFromServerSaga, args),
            cancel: take(cancelGettingDataFromServer.toString()),
        });
    });
}

This is the part that cancels the saga. First we listen the get action with takeLatest (you can also use takeEvery base on your needs). After that with race we are saying that either one of them is finished, cancel the other one. Now we dispach the cancel action where ever to cancel the whole thing.

useEffect(() => {
   /* get data from server here*/
    return () => {
       /* cancel it here*/
        dispatch(cancelGettingDataFromServer());
    };
}, []);

Using "componentWillUnmount" version of useEffect is best solution for my problem.

Hopefully this help saving some of your time.