Navigate back to the homepage

Polling with Redux Sagas

Tori Holmes-Kirk
March 14th, 2021 · 2 min read

Polling with Redux Sagas

When developing an app, you may run into a case where you need to wait for a long running process to complete. A common pattern for dealing with such a case is polling. In this pattern data is repeatedly requested from a source, such an an API, at intervals. Usually, polling continues until the long running process is completed or cancelled.

For this tutorial we will consider the use case of ordering a pizza. Many pizza delivery sites now offer order tracking which update you on the progress of pizza from order submission to the moment it is delivered at your door. This tutorial uses a very simplified version of this process to show how you can use a polling pattern with Redux Sagas to update your user interface as the order progresses.

Sample App Progress Screen

You can download the sample project for this demo from https://github.com/supertorio/redux-sagas-polling-tutorial

Note: the sample project was set up using CRA with a template for Typescript + Redux-toolkit, but you could just as easily accomplish this without typescript and using basic redux. I assume you already have familiarity with using redux and sagas and won’t be covering any project setup.

Breaking the Problem Down

When the user clicks the submit order button, we expect that an action is dispatched with a connected saga side effect. Inside that initial saga:

// 1. The order is submitted to an api and receives back an order number and the initial order status.

// 2. The store is updated with the new information.

// 3. Call pizzaStatusWatchWorker saga to begin polling

1// Default Saga
2export function* orderSaga() {
3 yield takeLatest(actions.submitOrder.type, submitOrder)
4}
5
6export function* submitOrder(action: PayloadAction<string>) {
7 try {
8 // 1. Submit Order
9 const { orderNumber, orderStatus } = yield call(
10 mockPostOrderAPI,
11 action.payload
12 )
13
14 //2. Process Response
15 yield put(actions.submitOrderComplete({ orderNumber, orderStatus }))
16
17 //3. Start Polling
18 yield call(pizzaStatusWatchWorker)
19 } catch (error) {
20 yield put(
21 actions.submitOrderComplete({ orderNumber: "", orderStatus: "Error" })
22 )
23 }
24}

The polling watch worker saga (pizzaStatusWatchWorker) utilises the racing effects concept. This allows for the start of two simultaneous processes. When one process is completed, the other is automatically cancelled. For this race we need to:

// 4. Trigger a call effect called ‘task’ to start a polling task. The pizzaStatusPollingWorker saga runs as an infinite loop, so it will never finish before the other process.

// 5. Start a take effect called ‘cancel’ which waits and looks for the cancel action to be dispatched.

1// Name of action which ends the polling
2const CANCEL_STATUS_POLLING = "CancelStatusPolling"
3
4export function* pizzaStatusWatchWorker() {
5 // Race starts two concurrent effects. We start our polling effect 'task'. As soon
6 // as the take effect 'cancelled' is triggered, the 'task' will be cancelled.
7 yield race({
8 //4. Start the polling worker
9 task: call(pizzaStatusPollingWorker),
10 //5. Start a take effect waiting for the cancel action.
11 cancel: take(CANCEL_STATUS_POLLING)
12 })
13}

Finally in the pizzaStatusPollingWorker, is where the work of getting the next status and updating the store at an interval is accomplished

// 6. The contents are wrapped in a while statement that should run indefinitely

// 7. Fetch the new status from the API using the current order number

// 8. Update the store with new status

// 9. Check if a status is encountered that triggers an end to the polling

// 10. Dispatch the cancel action if the polling is done

// 11. Delay before the next polling iteration

1// Time between polling iterations in MS.
2const POLLING_DELAY = 2500
3
4export function* pizzaStatusPollingWorker() {
5 // 6. Run indefinitely
6 while (true) {
7 try {
8 // 7. Fetch the new status from the API using the current order number
9 const currentOrderNumber: string = yield select(selectOrderNumber)
10 const newStatus: OrderStatus = yield call(
11 mockOrderStatusAPI,
12 currentOrderNumber
13 )
14
15 // 8. Update the store with new status
16 yield put(actions.updateOrderStatus(newStatus))
17
18 // 9. Check if a status is encountered that triggers an end to the polling
19 if (newStatus === "Complete" || newStatus === "Error") {
20 // 10. Dispatch the cancel action if the polling is done
21 yield put({ type: CANCEL_STATUS_POLLING })
22 } else {
23 // 11. Otherwise, delay before the next polling iteration
24 yield delay(POLLING_DELAY)
25 }
26 } catch (error) {
27 // Catch any unexpected errors and cancel polling if something goes wrong.
28 console.error(error)
29 yield put({ type: CANCEL_STATUS_POLLING })
30 yield put(actions.updateOrderStatus("Error"))
31 }
32 }
33}

These three sagas work together to provide a clean and testable way to achieve the polling pattern in your redux app.

What’s Next?

As you can see, with a couple built in saga effects, its easy and straightforward to implement a polling pattern in your redux-sagas app. You can extend this technique further to solve for a wide variety of use cases where keeping your client app and server in sync is critical.

More articles from Tori Holmes-Kirk

Merging Cells in Excel with UiPath

Using Invoke VBA in UIPath studio to script Excel actions.

June 20th, 2019 · 2 min read

Welcome

Thank you for visiting my site. Over the coming months I hope to share my experiences and tutorials through this website.

May 5th, 2019 · 1 min read
© 2019–2021 Tori Holmes-Kirk
Link to $https://supertorio.medium.com/Link to $https://twitter.com/supertorioLink to $https://github.com/supertorioLink to $https://linkedin.com/in/supertorioLink to $https://supertor.io