How to Implement Redux Saga in React

This article provides a straightforward, step-by-step guide on how to integrate and use Redux Saga in a React application. You will learn how to install the necessary packages, configure the Redux store with the saga middleware, write generator functions to handle asynchronous side effects, and dispatch actions from your React components to trigger these sagas.

Step 1: Install the Required Packages

To get started, you need to install Redux Toolkit (the recommended way to write Redux logic), React-Redux, and Redux Saga. Run the following command in your terminal:

npm install @reduxjs/toolkit react-redux redux-saga

Step 2: Create the Redux Slice

Create a Redux slice to manage the state and reducers. For this example, we will fetch user data from an API. Create a file named userSlice.js:

import { createSlice } from '@reduxjs/toolkit';

const userSlice = createSlice({
  name: 'user',
  initialState: {
    data: null,
    loading: false,
    error: null,
  },
  reducers: {
    fetchUserRequest: (state) => {
      state.loading = true;
      state.error = null;
    },
    fetchUserSuccess: (state, action) => {
      state.loading = false;
      state.data = action.payload;
    },
    fetchUserFailure: (state, action) => {
      state.loading = false;
      state.error = action.payload;
    },
  },
});

export const { fetchUserRequest, fetchUserSuccess, fetchUserFailure } = userSlice.actions;
export default userSlice.reducer;

Step 3: Create the Saga

Sagas use ES6 generator functions to handle side effects. Create a file named userSaga.js to define the worker saga (which performs the API call) and the watcher saga (which listens for dispatched actions).

import { call, put, takeLatest } from 'redux-saga/effects';
import { fetchUserRequest, fetchUserSuccess, fetchUserFailure } from './userSlice';

// Mock API function
const fetchUserDataApi = async () => {
  const response = await fetch('https://jsonplaceholder.typicode.com/users/1');
  if (!response.ok) {
    throw new Error('Failed to fetch user');
  }
  return response.json();
};

// Worker Saga
function* workFetchUser() {
  try {
    const userData = yield call(fetchUserDataApi);
    yield put(fetchUserSuccess(userData));
  } catch (error) {
    yield put(fetchUserFailure(error.message));
  }
}

// Watcher Saga
function* userSaga() {
  yield takeLatest(fetchUserRequest.type, workFetchUser);
}

export default userSaga;

Step 4: Configure the Store with Saga Middleware

Next, create the Redux store, instantiate the saga middleware, and run your watcher saga. Create a file named store.js:

import { configureStore } from '@reduxjs/toolkit';
import createSagaMiddleware from 'redux-saga';
import userReducer from './userSlice';
import userSaga from './userSaga';

const sagaMiddleware = createSagaMiddleware();

const store = configureStore({
  reducer: {
    user: userReducer,
  },
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware({ thunk: false }).concat(sagaMiddleware),
});

sagaMiddleware.run(userSaga);

export default store;

Step 5: Provide the Store to Your React App

Wrap your root React component with the Provider from react-redux and pass the configured store as a prop. Modify your main.js or index.js file:

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

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

Step 6: Dispatch the Action from a Component

Finally, use the useDispatch hook to trigger the saga and the useSelector hook to read the state inside a React component. Create or update your App.jsx file:

import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { fetchUserRequest } from './userSlice';

function App() {
  const dispatch = useDispatch();
  const { data, loading, error } = useSelector((state) => state.user);

  return (
    <div style={{ padding: '20px' }}>
      <h1>Redux Saga Integration</h1>
      <button onClick={() => dispatch(fetchUserRequest())} disabled={loading}>
        {loading ? 'Loading...' : 'Fetch User Data'}
      </button>

      {error && <p style={{ color: 'red' }}>Error: {error}</p>}
      
      {data && (
        <div style={{ marginTop: '20px' }}>
          <h3>User Details:</h3>
          <p><strong>Name:</strong> {data.name}</p>
          <p><strong>Email:</strong> {data.email}</p>
          <p><strong>Website:</strong> {data.website}</p>
        </div>
      )}
    </div>
  );
}

export default App;