How To Use Redux Saga/Typescript Like a pro! II

As we saw app-component
has tree Routes. “First” route is the landing page. On this route we are going to fetch all the lists when the ComponentDidMount.
This is Kind of like a layout for a functional component:
import * React from "react";
import { RouteComponentProps } from "react-router-dom";
import { NewList, ListsState, ListState } from "@eneto/api-client";
This Component is a lil bit special as it is a route it comes with some special props such as history, location match and so on.
export interface LandingPageProps extends RouteComponentProps {
lists: ListState[];
getListsRequests: ListsAction;
getListRequests: ListsAction;
postList: ListsAction;
}
- lists — The array of lists stored in DB.
- getListsRequests — Action creator that will trigger the Fetching.
- getListRequests – Triggers the fetching of an specific list.
- postList – Inserts new list.
PS: I usually don’t create the actions like this, but I just wanted to try some new
As this actions are related to lists then we should add that into the ./src/modules/lists/lists-actions.ts
import { AppAction } from "@eneto/api-client";export function listsAction<T, P> (type: T, payload: P): AppAction <T, P> {
return {
type,
payload,
};
} export type ListsAction = typeof listsAction;
This is going to be the only Action Creator for lists-actions! :3.
To dispatch this action we need to dispatched it from a container, as containers are the ones connected to redux.
./src/containers/lists-container.ts
import { connect } from "react-redux";
import { AppState } from "@eneto/api-client"; import { LandingPage } from "../components/lists"; import { lists } from "../modules/lists"; import { listsAction } from "../modules/lists/lists-actions";
const stateToProps = (state: AppState) => ({
lists: [...lists(state)]
});
const dispatchToProps = {
getListsRequests: listsAction,
getListRequests: listsAction,
postList: listsAction,
};
export default connect(stateToProps, dispatchToProps)(LandingPage);
As we are going to pass the list of lists as prop, we need to get that selector from the store.
To do so we are going to make use of a module call: reselect
this module to simplify what it does. it memorizes the selector and if it has not been modified. it just returns what it has, there’s no need to recompute if heavy process executes to get the value… not the case for our example, but does not hurt to have it.
// ./src/modules/lists/lists-selectors.tsimport { AppState, ListState } from "@eneto/api-client";
import { Selector } from "react-redux";
import { createSelector, OutputSelector } from "reselect";
We are going to separate the selector with the memorization.
// selector
const getLists: Selector<AppState, ListState[]> = (state: AppState): ListState[] => state.lists.lists;
The selectors get the entire state as param.
export const lists: OutputSelector<AppState, ListState[], (res: ListState[]) => ListState[]> = createSelector<AppState, ListState[], ListState[]>([getLists], (lists: ListState[]) => lists);
[createSelector] get two parameters. the first one is a selector or an array of selectors, the second one is a callback.
if we reopen lists-container.ts
and lets make use of it.
...
const stateToProps = (state: AppState) => ({
lists: [...lists(state)]
});
...
At the Representational Component.
...export function LandingPage (props: LadingPageProps): React.ReactElement<LadingPageProps> { const [ listName, setListName ] = React.useState<string>("");
const [ listDescription, setListDescription ] = React.useState<string>("");
const [renderList, setRenderList] = React.useState<ListState[]>([]);// ComponentDidMount
React.useEffect(() => {
const { getListsRequests } = props;
getListsRequests(GET_LISTS_REQUEST, undefined);
},[])//DidComponentUpdate
React.useEffect(() => {
setRenderList([...lists])
}, [lists]); return (
.....
);}
In the same function LandingPage
let’s add the onSubmitHandler
...
const onSubmitListHandler = (event: React.MouseEvent<HTMLButtonElement, MouseEvent>): void => {
event.preventDefault();
const { postList } = props; postList(POST_LISTS_REQUEST, {
name: listName,
description: listDescription
} as NewList);
};
...
So Far we have getAllLists
action & postList
action, but if you notice. nothing happends. if we open the lists-reducer.ts

in the switch
we only have GET_LISTS_SUCCESS
& CLEAN_CURRENT_LIST
, but if you open the presentational component, it is calling GET_LISTS_REQUEST

Open ./src/utils/constants.ts, you’ll notice some:

Every single “Action” or Effects as that’s how actions are call. Well… each one has a: Request,Success,Failed
State.
Well yes, not all of them has them three actions. only the ones that are thunks AKA: async.

I knd of lie a lil bit, if we look at the switch carefully we’ll C

default, which means that even tho we did not write down GET_LIST_REQUEST
it is been register and as we do not do anything with it let it just fall into the default hihihih :3 so why we need a request and a failed… I mean you always have to handle exceptions.
The magic radicates within the sagas, but if we recap we are going to use two different type of sagas. Watchers/Workers.
Open ./src/modules/lists/lists-watchers.ts

There are all our REQUESTs ones. to listen to each request, we need to add its own watcher
/**
* Spawns a `saga` on each action dispatched to the Store that
* matches `pattern`.
*/
function* getAllListsWatcher () {
yield takeEvery(GET_LISTS_REQUEST, getAllListsWorker);
}
As I mentioned at the end of How To Use Redux Saga/Typescript Like a pro! I.
To handle CONCURRENCY there are two helpers
takeEvery
takeEvery
allows multiplesaga
tasks to be forked concurrently.
takeLatest
takeLatest
doesn't allow multiple Saga tasks to be fired concurrently. As soon as it gets a new dispatched action, it cancels any previously-forked task (if still running).
takeLatest
can be useful to handle AJAX requests where we want to only have the response to the latest request.
So in less words every time the action/effect GET_LISTS_REQUEST
is triggered the getAllListsWorker
will run.

we is already here, so let’s do postListWorker.
when we call that Action in the component we past the NewUser
as param.
let’s remember that this function is none but a middleware. in other words, postListWorker will be call right b4 the reducers switch is call. there for we have the action (Type & Payload). To access it set it as param.
export function* postLists (action: AppAction<POST_LISTS_REQUEST, NewList>) {
try {
const { payload } = action;
// Post Request
const res = yield call(listApi.postNewList, payload.name,
payload.description); if (res === 201) {
yield put(listsAction(GET_LISTS_REQUEST, undefined));
} yield put(listsAction(POST_LISTS_SUCCESS, res));
} catch (error) {
yield put(listsAction(POST_LISTS_FAILED, error));
}
}
import { call, put } from “redux-saga/effects”;
call and put are imported from redux-saga/effects in case you wonder :3 also remember that CALL are blocking effects, but I told you that we were going to use only nonblocking effects!
thanks to the help of fork
we is able to do so.
at the top of your watchers file we is going to import fork
import { fork, ForkEffect, takeEvery, takeLatest } from "redux-saga/effects";
and we is going to export the watchers as an array of ForkEffect
export const listsWatchers: ForkEffect[] = [
fork(getAllListsWatcher),
fork(getListWatcher),
fork(postListsWatcher),
fork(deleteListsWatcher),
];
The only problem with this is that as each saga is been fork
on a separate thread
there is not really a difference if we use takeEvery || takeLatest
thats why I will remove takeLatest

Now let’s add the ForkEffect
array into the rootSagas
./src/modules/index.ts
import { all } from "redux-saga/effects";
import {
userActions, userApi, userReducer, usersWatchers
} from "./users";
import {
listActions, listApi, listReducer, listsWatchers,
} from "./lists";function* rootSagas () {
const watchers = [...listsWatchers, ...usersWatchers];
yield all(watchers);
}export {
userActions,
userApi,
userReducer,
rootSagas,
listActions,
listApi,
listReducer,
listsWatchers,
};
At ./src/index.tsx
we import rootSagas

Now you is ready to complete the whole project by your own!