How To Use Redux Saga/Typescript Like a pro! I
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.
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.
- - Action
- - Dispatcher
- - Store
I mean, in reality you do not even need redux saga at all.
What the Hell is redux saga, anyway:
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!….
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.
Getting back to the source view. index.tsx has imported <App />
app-component is the main component in which all our shit will live 🙆♀️
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.
Our app only has two modules:
- User Modules
- Lists Modules
What this Dirs have ?
- index — One 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
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 invokemyfunc('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