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

Ernesto Jara Olveda
9 min readMay 4, 2020

--

Redux Saga

This could be part two of my other story call: How to Use Axios/Typescript like a pro!.

Here you’ll find Second-Part

If you want to pass by and show some love I’ll really appreciate :3 no further a do.

Well first things first. As I am going to skip a lot of the boiler plate things we need to do. I really recommend you to clone this repo:

$ git clone git@github.com:EnetoJara/agenda.git && cd agenda && npm i --prefix ./client& npm i --prefix ./api-rest;

That command will clone the repo as well as installing the dependencies from client & rest-api.

Flux

While that is been cloned, let’s take some theory out of the way. If you have used react in the past the image of the left will be very familiar to you. It is the one way binding the old flux gave us.

And on my own words and who I see it is: “No matter what you do, you always have to follow the same path”… xDDD not very clear I know… Well given that very very poor explanation of flux pattern. We need to follow this 3 things.

  1. - Action
  2. - Dispatcher
  3. - Store
redux saga flow

I mean, in reality you do not even need redux saga at all.

What the Hell is redux saga, anyway:

Is a library that aims to make application side effects (i.e. asynchronous things like data fetching and impure things like accessing the browser cache) easier to manage, more efficient to execute, easy to test, and better at handling failures.

Why would I use that if I already know redux thunk. Well let Redux Saga tell’s you why.

Contrary to redux thunk, you don’t end up in callback hell, you can test your asynchronous flows easily and your actions stay pure.

What makes Sagas much better option than thunk.

The mental model is that a saga is like a separate thread in your application that’s solely responsible for side effects. redux-saga is a redux middleware, which means this thread can be started, paused and cancelled from the main application with normal redux actions, it has access to the full redux application state and it can dispatch redux actions as well.

WOW! WOW! WOW!….

iant no body got time for that.

What do you mean by “Separate thread”.
every body knows that JavaScript is single threaded. How is then that even possible, how sagas run on separate threads ?

Yes no matter What people says JavaScript is single threaded, but thanks to:

an ES6 feature called Generators to make those asynchronous flows easy to read, write and test. (if you’re not familiar with them here are some introductory links) By doing so, these asynchronous flows look like your standard synchronous JavaScript code. (kind of like async/await, but generators have a few more awesome features we need).

YES!! the real power of sagas is thanks to “GENERATORS”. I need you to pause for a minute an please follow the instructions at the READM3.md

this are the verions of node Ill use. Also I am assuming you have docker install in your machine, as you’ll need it to run the DB.

The repo has three main sections

  • api-rest — node api.
  • client — react app.
  • podtgresql — docker-compose.yml

To run DB get inside of the Postgres dir and run

$ cd postgres && docker-compose up --build -d

That command will run on ditach more or how ever u spell it.

Once it is build and running let’s run the api.

$ npm start

You can open another terminal or in your vscode 🤷‍♀️

Get in side of client dir and run

$ npm start

As the main purpose of this thing is focused on redux-saga, let’s identify how the code is structured and how to follow the tree.

As almost everywhere our entry point is the index.tsx

import * as React from “react”;
import * as ReactDOM from “react-dom”;
import { Provider } from “react-redux”;
import * as worker from “./serviceWorker”;
import { App } from “./components/app”;
import “./styles/index.scss”;
import { store } from ‘./redux/store/dev’;
import { rootSagas } from “./modules”;
const theStore = store();
theStore.runSaga(rootSagas);
ReactDOM.render(
<Provider store={ theStore }>
<App />
</Provider>,
document.getElementById(“root”));
worker.unregister();

As you notice it. There is nothing special.

The only three files/imports that need you to pay attention to are.

  • app-component
  • rootSagas
  • store

The first file we are going to inspect is the store.

It is just as similar as a configureStore that uses thunk

...
import
createSagaMiddleware from "redux-saga";
...

But of course we need redux saga.

...
export function store(initialState: AppState = init) {
/**
Creates a Redux middleware and connects the Sagas to the Redux Store
**/
const sagaMiddleware = createSagaMiddleware();
...

Like always we return the createStore with all its goodies, as well as runSagas.

return {
...createStore(rootReducer, initialState, composeWithDevTools(applyMiddleware(...middleware))),
/**
Dynamically run saga. Can be used to run Sagas only after the applyMiddleware phase.
The method returns a Task descriptor.
**/
runSaga: sagaMiddleware.run
};

saga must be a function which returns a Generator Object. The middleware will then iterate over the Generator and execute all yielded Effects.

In the first iteration, the middleware invokes the next method to retrieve the next Effect. The middleware then executes the yielded Effect as specified by the Effects API below. Meanwhile, the Generator will be suspended until the effect execution terminates. Upon receiving the result of the execution, the middleware calls next(result) on the Generator passing it the retrieved result as an argument. This process is repeated until the Generator terminates normally or by throwing some error.

src

Getting back to the source view. index.tsx has imported <App />

app-component.tsx

app-component is the main component in which all our shit will live 🙆‍♀️

split

The app-component implements React Router Dom each route is a Container which is dynamic loaded. I’m pretty sure that react can handle lazy loading “Natively”, but the module that I have been using for a couple of years now is call: react-loadable.

react-loadable: is a higher order component for loading components with dynamic imports.

The HOC gets two parameters.

  • loader: function that returns the dynamic component.
  • loading: component that will be rendered until the dynamic one is been loaded.

If you want your chunk to have an specific name, all you need to do is to set a comment: /* webpackChunkName: “name-you-want” */

import(/* webpackChunkName: "lists" */, "path-to-component");

Time to open a container, let’s see how it is built.

What is the container, the container is an special kind of component, but this ones shouldn’t be used to render anything, think of containers as the bridge between the beauty of a page and the logic of it.
That’s why we only have a HOC that connects to redux, maps the state to the presentational component props as well as dispatching actions and at the end. The Component rendered comes from ./src/components/lists of course the more crap you have on your state the bigger this thing gets, much much cleaner than have the jxs, the methods, I mean all the crap in a single file.

And Last, but not least! The main part of the whole project my own poor implementation of the Redux Ducks, if you want to know how to really apply ducks Scaling your Redux App with ducks. It is a very good pattern to scale large apps, but it is not only good scaling it also does not affect to much to the performance of the app on our case, because each Route is been loaded dynamically so the end bundle is not going to be all at a single monster file, but spread on multiple smaller files. BUT! as there is not such thing as a silver bullet, we need to pay attention, as we could end up overheating the server.

modules

Our app only has two modules:

  • User Modules
  • Lists Modules

What this Dirs have ?

modules
  • indexOne of the problems that comes with Ducks is the fact that as every thing is kind of isolated. there will be times in which a module needs something from another module, so the paths of your files can get quite large.
  • reducer — Well you know what is thing is :3
  • actions — Actions are the activities, functions, you name them.
  • api HTTP request handlers.
  • selectors — The Store is not only the place where you data is stored, but also the only one that SHOULD KNOW how is it structured. there is a library that on my own prospective it is a MUST reselect.
  • watchers/workers: Refers to a way of organizing the control flow using two separate Sagas.

B4 we endup this theorycal part and move to the practical one. To “simplify” Redux-Saga there are only two different type of effects

An effect is a plain JavaScript Object containing some instructions to be executed by the saga middleware.

You create effects using factory functions provided by the redux-saga library. For example you use call(myfunc, 'arg1', 'arg2') to instruct the middleware to invoke myfunc('arg1', 'arg2') and return the result back to the Generator that yielded the effect

😨👌 WAIT!!!!

🤔👌 If there are only two types/kinds of Effects and there is a Watcher and Worker. those things must be related.

NOTE: Personal way of looking at this whole thing

The effects are:

  • Blocking
  • NonBlocking

🙃 TROLL!!!

For the blocking ones some of them are:

  • take: Blocking: will wait for the action
  • call: Blocking: will wait for ApiFn (If ApiFn returns a Promise)
  • join: Blocking: will wait for the task to terminate

Non-Blocking some are:

  • put: Non-Blocking: will dispatch within internal scheduler
  • fork: Non-blocking: will not wait for otherSaga
  • cancel: Non-blocking: will resume immediately

Now we know which ones block the main thread and which ones don’t and if we pay around with them will make a non blocking flow. to help us out with the concurrency we’ll use this two other guys.

takeEvery

takeEvery allows multiple saga 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.

Now we are ready to start. wait for part three because we are going to develop the repo

PART-II

--

--

No responses yet