Must-Know React Libraries - Redux-Saga

Redux Saga

Redux-Saga is a library written in JavaScript that facilitates state management and managing side effects in React applications. Here are some key points about what Redux-Saga does:

  1. Side Effect Management: It manages side effects such as API calls, timeouts, and other asynchronous operations.
  2. Clean and Testable Code: It makes the code written to manage side effects cleaner and more testable.
  3. Asynchronous Operations: It manages asynchronous operations more easily with Redux actions.
  4. Flexible Control: It provides greater control over capturing and processing actions.
  5. Manageable Flows: It makes complex flows and business logic more manageable.

Redux-Saga works using generator functions, providing developers with great flexibility in managing complex asynchronous workflows in their applications. For example, you can use Redux-Saga to send a specific action when making an API call, and then send different actions based on whether the call is successful or fails.

Redux-Saga Example

An application where a switch button sends the light status to the backend and updates the switch status when a positive response is received from the backend:

1. App.js

import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { toggleLightRequest } from './redux/actions';

function App() {
  const dispatch = useDispatch();
  const lightStatus = useSelector(state => state.lightStatus);
  const loading = useSelector(state => state.loading);

  const handleToggle = () => {
    dispatch(toggleLightRequest(!lightStatus));
  };

  return (
    <div>
      <h1>Light Status: {lightStatus ? 'On' : 'Off'}</h1>
      <button onClick={handleToggle} disabled={loading}>
        {loading ? 'Updating...' : 'Change Status'}
      </button>
    </div>
  );
}

export default App;

2. redux/actions.js

export const TOGGLE_LIGHT_REQUEST = 'TOGGLE_LIGHT_REQUEST';
export const TOGGLE_LIGHT_SUCCESS = 'TOGGLE_LIGHT_SUCCESS';
export const TOGGLE_LIGHT_FAILURE = 'TOGGLE_LIGHT_FAILURE';

export const toggleLightRequest = (status) => ({
  type: TOGGLE_LIGHT_REQUEST,
  payload: status,
});

export const toggleLightSuccess = (status) => ({
  type: TOGGLE_LIGHT_SUCCESS,
  payload: status,
});

export const toggleLightFailure = () => ({
  type: TOGGLE_LIGHT_FAILURE,
});

3. redux/reducer.js

import { TOGGLE_LIGHT_SUCCESS, TOGGLE_LIGHT_FAILURE, TOGGLE_LIGHT_REQUEST } from './actions';

const initialState = {
  lightStatus: false,
  loading: false,
  error: null,
};

export const lightReducer = (state = initialState, action) => {
  switch (action.type) {
    case TOGGLE_LIGHT_REQUEST:
      return { ...state, loading: true };
    case TOGGLE_LIGHT_SUCCESS:
      return { ...state, lightStatus: action.payload, loading: false };
    case TOGGLE_LIGHT_FAILURE:
      return { ...state, loading: false, error: 'Could not update light status.' };
    default:
      return state;
  }
};

4. redux/sagas.js

import { takeLatest, call, put } from 'redux-saga/effects';
import { TOGGLE_LIGHT_REQUEST, toggleLightSuccess, toggleLightFailure } from './actions';

function* toggleLightSaga(action) {
  try {
    const response = yield call(apiToggleLight, action.payload); // Send request to the backend
    if (response.ok) {
      yield put(toggleLightSuccess(action.payload)); // Update status if successful
    } else {
      yield put(toggleLightFailure());
    }
  } catch (error) {
    yield put(toggleLightFailure());
  }
}

function apiToggleLight(status) {
  return fetch('/api/toggle-light', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ status }),
  });
}

export default function* rootSaga() {
  yield takeLatest(TOGGLE_LIGHT_REQUEST, toggleLightSaga);
}

5. redux/store.js

import { createStore, applyMiddleware } from 'redux';
import createSagaMiddleware from 'redux-saga';
import { lightReducer } from './reducer';
import rootSaga from './sagas';

const sagaMiddleware = createSagaMiddleware();
const store = createStore(lightReducer, applyMiddleware(sagaMiddleware));

sagaMiddleware.run(rootSaga);

export default store;

6. index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import App from './App';
import store from './redux/store';

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);

Backend Example (/api/toggle-light)

app.post('/api/toggle-light', (req, res) => {
  const { status } = req.body;
  // Add backend logic here (e.g., save to database)
  res.json({ success: true });
});

In this example, there is a switch button in App.js. When pressed, the light status is sent to the backend through Redux-Saga, and if the operation is successful, the status of the button is updated.

Information Flow

The information flow in this example occurs as follows:

  1. App.js
    dispatch(toggleLightRequest(!lightStatus));
    → When the button is pressed, the TOGGLE_LIGHT_REQUEST action in actions.js is dispatched.
  2. redux/actions.js
    The toggleLightRequest function is called.
    return { type: TOGGLE_LIGHT_REQUEST, payload: status }; sends the new status to lightReducer in redux/reducer.js.
  3. redux/reducer.js
    The lightReducer captures the TOGGLE_LIGHT_REQUEST action from actions.js.
    → The loading status is updated to true.
  4. redux/sagas.js
    The takeLatest(TOGGLE_LIGHT_REQUEST, toggleLightSaga) captures the TOGGLE_LIGHT_REQUEST action from actions.js.
    → The toggleLightSaga function runs.
  5. redux/sagas.js
    const response = yield call(apiToggleLight, action.payload);
    → The apiToggleLight function is called to send a request to the backend.
  6. redux/sagas.js
    function apiToggleLight(status)
    → A POST request is made to /api/toggle-light using the fetch API.
  7. Backend (/api/toggle-light)
    app.post('/api/toggle-light', (req, res) => {...})
    → The incoming request is processed, and the light status is saved.
  8. Backend (continuation)
    res.json({ success: true });
    → A success response is returned.
  9. redux/sagas.js
    if (response.ok) { yield put(toggleLightSuccess(action.payload)); }
    → When a successful response is received, the TOGGLE_LIGHT_SUCCESS action in actions.js is dispatched.
  10. redux/reducer.js
    The lightReducer captures the TOGGLE_LIGHT_SUCCESS action from actions.js.
    → The light status is updated, and the loading status is set to false.
  11. App.js
    → When the Redux state is updated, the component re-renders.
    → The light status and button status are updated (the text on the button changes).

Comments