Understanding state management and handling different flows of asynchronous calls with it is one of the most challenging aspects of developing an application for a front-end developer. To handle data fetching, we can leverage an existing state management library using a middleware. And this is where middlewares like Redux-Thunk and Redux-Saga come to the rescue.
Even though Redux Thunk is well suited for most of our asynchronous action needs, Sometimes it could become more complex as our business logic gets more complex. In those tough situations, we can rely on Redux Saga.
Redux Saga is a middleware library that allows a Redux store to interact with resources outside of itself asynchronously. This includes making HTTP requests to external services, accessing browser storage, etc. These operations are also referred to as side effects. Redux Saga assists in handling these side effects in a more manageable manner.
Here is an illustration of how middleware flow works.
What problem are we trying to resolve here?
When developing applications, there can be situations where we must wait for some process to be completed and our frontend needs to be notified.
As an example, consider paying for a cart, we need to wait for a response from backend to redirect user from payment page to order confirmation page. And to deal with such situations, we can embrace technologies like Websockets. When that's not available as an option, we implement a technique known as Polling.
From a JavaScript perspective, polling can be defined as periodically making API calls to the back-end and checking the response of the same. And it keeps going until we cancel it based on its status.
The Scenario
Consider a scenario where on a button click we must begin polling and enable a user to view the results of an API call every 3 seconds. And when the user clicks on the Stop Polling
button, we should be able to stop the polling.
The Solution
Let's move on to the code as we will be putting a solution to the same issue into practise in this article. We'll start by building a React application in which we will fetch data from Random Quote Generator API.
npx create-react-app <project-name>
cd <project-name>
Now we will install all of the project's dependencies, viz. redux-toolkit
, redux-saga
and react-redux
.
npm i @reduxjs/toolkit
npm i redux-saga
npm i react-redux
The normal method will involve calling the API directly from within our component code, however in this case we execute a redux action and shift the data fetching mechanism to a saga.
Now let's configure our reducers, which will, in response to an action
, return a state
. Additionally, we can include the loading
and polling
states.
I know that everyone wants to see the magic work and the Redux-Saga begin. However, before we do so, we must first setup the component that will call the API and retrieve the data for us. Let's do that first.
Redux Saga Setup
A few things need to be discussed with you before we move on to the Redux-Saga Polling. “Sagas are implemented as generator functions that yield objects to the redux-saga middleware.”
And the saga is divided into two parts. One is the watcher function, while the other is the worker function.
The Watcher Saga takes care of every action that is dispatched to the redux store, and if it matches the action it is supposed to handle, it will allocate it to its worker saga. The worker saga handles the action and completes all of the tasks, as well as the expected side effects.
Now, let's get into the code.
Note: A Blocking call indicates that the Saga has yielded an Effect and will wait for the result of its execution before proceeding to the next instruction inside the yielding Generator. Example (take
: Waits for an action, call
: Waits for the promise to resolve)
A non-blocking call indicates that the Saga will resume immediately and it will not wait. Example (fork
: Will not wait for other Saga)
Let's finally set up our store
so that we can execute everything.
And this is the user interface component from which we will dispatch
the action when a button is clicked.
Also, in index.js
we need to wrap our App.js
in Provider
The flow
a. When the user clicks the Start Polling
button, the action startPolling
will be dispatched.
b. Once it has been dispatched, the take("START_POLLING)
in the watcher saga quotePollSaga
will be yielded, which will trigger the background polling by forking in fork(quoteStatusCheckLoop)
and we get the reference to the fork in quotePollTask
.
c. On the background: The polling will begin in the worker saga quoteStatusCheckLoop
, which will run the while
statement in an infinite loop. For each while loop; 1) it will call fetchQuoteData
using a call
effect which will resume the execution once it resolves the promise with response. 2) The store
will be updated using the GET_QUOTES_SUCCESS
action. 3) And then wait for 3 seconds.
d. Now back to quotePollSaga
: We started the fork and now we're waiting for the action STOP_POLLING
. The action is dispatched when the user clicks on the Stop Polling
button. This line will yield yield take("STOP_POLLING")
and resume the generator to the next line.
e. When a user clicks the Stop Polling
button, the action stopPolling
is dispatched, and the next line is yield cancel(quotePollTask)
will be triggered, which cancels the running background task.
f. Lastly, back to the background task: Once cancelled, whatever is running in the task, even if we're waiting for the API response, will be cancelled and the execution will be jumped to the finally
block. We can handle the cancelled task by using yield cancelled()
. But we do not need to handle it.
Brownie Point
Instead of using buttons, we can also use the useEffect
hook to start polling when a component mounts
and terminate polling when a component unmounts
.
Conclusion
Thanks to a few in-built saga effects, implementing a polling pattern in our apps is straightforward and declarative. By transferring API polling from the component to the saga, it is feasible to separate concerns for handling APIs and rendering data into different files, which helps facilitate problem-solving. Here is the app we built together Redux-Saga Polling App Demo and Github
Quick overview of the effects we discussed above:
call
: run a method, Promise or other Saga.cancel
: cancels the saga execution.put
: dispatch an action into the store.take
: wait for a redux action/actions to be dispatched into the store.delay
: block execution for a predefined number of milliseconds.fork
: performs a non-blocking call to a generator or a function that returns a promise.