Redux saga

Redux sagas allow us to handle application side effects, such as asynchronous loading of data.

Assuming you have a React application created, we need run the following

  • yarn add react-redux
  • yarn add redux-saga

To create a simple demo, we’ll change the App.js file to

import React from 'react';
import store from './store';
import { Fetch } from './rootReducer';

function App() {

  function handleDoFetch() {
    store.dispatch({type: Fetch});
  }

  return (
    <div className="App">
      <button onClick={handleDoFetch}>Do Fetch</button>
    </div>
  );
}

export default App;

So this will simply dispatch an action which will ultimately be handled by our saga. Before this happens let’s create a redux store and set up the redux saga middleware, here’s my store.js file

import { createStore, applyMiddleware, combineReducers } from "redux";
import createSagaMiddleware from "redux-saga";
import rootReducer from "./rootReducer";
import rootSaga from "./rootSaga";

export const sagaMiddleware = createSagaMiddleware();

const store = applyMiddleware(sagaMiddleware)(createStore)(
  combineReducers({
    rootReducer,
  })
);

sagaMiddleware.run(rootSaga);

export default store;

We don’t need to combineReducers as there’s only one, but it’s there for an example of setting up multiple reducers. Let’s now create a very basic reducer named rootReducer.js

export const Fetch = "FETCH";
export const FetchEnded = "FETCH_ENDED";

export default (state = {}, action) => {
  switch (action.type) {
    case FetchEnded:
      console.log("Fetch Ended");
      return {
        ...state,
        data: "Fetch Ended"
      }
    default:
      break;
  }   
  return state;
}

Notice we’ve got two actions exported, Fetch and FetchEnded but there’s nothing handling Fetch in this case. This is because redux middleware will pass this through to the redux-saga we’re about to create. We could also handle Fetch here and still handle it also within the saga, the point being the saga is going to handle this action when it see’s it.

Now we’ve got everything in place, let’s put the final piece in place, the saga will be stored in rootSaga.js and here it is

import { put, takeLatest } from 'redux-saga/effects'
import { Fetch, FetchEnded } from "./rootReducer";

function *fetchData() {
    console.log("fetchData")
    yield put({ type: FetchEnded });
}

function* rootSaga() {
    yield takeLatest(Fetch, fetchData);
}

export default rootSaga;

Notice that the rootSaga function is a function generator and it yields the result of a call to fetchData each time the Fetch action is detected.

It might be the fetchData yield’s many values or even sits in a loop yielding data but in this example we’ve kept things simple. Running the application will display a button and using the browser’s dev tools, when the button is pressed the Fetch action is detected by the saga and the fetchData function runs, which then in turn dispatches a FetchEnded action which is handled by the reducer.

As stated, our fetchData is very simple but in a real world scenario this function could be acting as a websocket client and for every value returned would yield each value within a while(true) loop or the likes until cancelled or maybe an error ocurred.