loading...

Redux-Saga: What/Why/How + examples

tkudlinski profile image Tomasz Kudlinski ・2 min read

What

Redux-Saga is here for you to help with side effects in your app, especially if those side effects are impacting your global state - Redux store. It is a redux-middleware, which handle async tasks with javascript generators.

Why

You can think of Redux-Saga as of separate thread, where in synchronous way a very complex asynchronous scenarios can be described. Thanks to this it is much easier for developer to read, understand and maintain complex async business logic. In my experience, I have learnt that Redux-Saga is more developer friendly (after you will learn it basis) comparing to Redux Middleware or Redux Thunk

How

In main.js file plug Root Saga from saga.js to the store using applyMiddleware function.

main.js

import { createStore, applyMiddleware } from 'redux'
import createSagaMiddleware from 'redux-saga'

import reducer from './reducers'
import rootSaga from './sagas'

// create the saga middleware
const sagaMiddleware = createSagaMiddleware()
// mount it on the Store
const store = createStore(
  reducer,
  applyMiddleware(sagaMiddleware)
)

// then run the saga
sagaMiddleware.run(rootSaga)

Your Root Saga just combine all other sagas (generators).

sagas.js

export default function* rootSaga() {
  yield all([
    firstSaga(),
    ...
    actionWatcher(),
    ...
    lastSaga()
  ])
}

Examples

Fetch data

export function* fetchData(action) {
   try {
      // make API call using `Api.fetchUser` function (could be fetch) for 
      // url stored in variable `action.payload.url`
      const data = yield call(Api.fetchUser, action.payload.url)
      // send `FETCH_SUCCEEDED` action with payload `data`, it will reach 
      // all reducers and sagas that are 'listening' to it
      yield put({type: "FETCH_SUCCEEDED", data})
   } catch (error) {
      // send `FETCH_FAILED` action to inform about error with API call
      yield put({type: "FETCH_FAILED", error})
   }
}

Execute logic after multiple actions

function* businessFlow() {
  while (true) {
    // wait for the ACTION_1
    yield take('ACTION_1')
    // ... perform the logic
    // wait for the ACTION_2
    yield take('ACTION_2')
    // ... perform some other logic
  }
}

Race

function* someTask() {
  while (true) { ... }
}

function* watchTask() {
    // `someTask` will be cancelled when CANCEL_TASK will be caught by the 
    // Saga
    yield race({
      task: call(someTask),
      cancel: take('CANCEL_TASK')
    })
}

Cancel previous call

If multiple GET_DATA actions will be fired, only the last one will be processed (all previous calls of fetchData will be cancelled)

function* fetchData() {
  try {
      const data = yield call(Api.fetchUser, action.payload.url)
      yield put({type: "FETCH_SUCCEEDED", data})
   } catch (error) {
      yield put({type: "FETCH_FAILED", error})
   }
}
function* actionWatcher() {
     yield takeLatest('GET_DATA', fetchData)
}

This was only short highlight of the Redux-Saga, for more check the docs

Posted on May 12 by:

tkudlinski profile

Tomasz Kudlinski

@tkudlinski

Fullstack Engineer #react #typescript #node.js #react-native #redux #graphql #apollo

Discussion

markdown guide