Working with Redux Saga: Making API Workflows Smooth
First of all, I’m no expert, but I’ll try my best to explain how you can use Redux Saga to handle side effects in your React or React Native project. If your application involves a lot of API calls, Redux Saga can simplify the process and make your code more manageable.
Why Redux Saga?
Redux Saga is a middleware that helps manage side effects like API calls in a clean and predictable way. Instead of cluttering your components or reducers with API logic, you can delegate these tasks to sagas. Sagas allow you to write asynchronous code that looks synchronous, using special JavaScript functions called generators.
Setting Up Folder Structure
Before diving into sagas, it’s important to organize your project for scalability. Here's a basic structure I follow:
Services: Contains the API endpoints and methods for calling them.
Reducers: Manages state changes based on API responses.
Sagas: Handles the side effects (like API calls) and coordinates with reducers.
- Services: Handling API Endpoints The Services folder contains reusable functions to make API calls. Here's an example for a "Change Password" API:
// ChangePasswordService.js
import {Constants} from '../../Config';
import Ajax from './base';
const BASE_URL = Constants.IS_DEVELOPING_MODE
? Constants.BASE_URL.DEV
: Constants.BASE_URL.PROD;
export default {
ChangePassword: async params => {
return fetch(`${BASE_URL}api/change-password`, {
method: 'POST',
body: params,
headers: {
Accept: 'multipart/form-data',
},
})
.then(response => Ajax.handleResponse(response))
.then(data => {
console.log('Data', data);
return data;
});
},
};
Here, we define a ChangePassword function to make an API call. It uses fetch to send a POST request to the endpoint and handles the response using a helper (Ajax.handleResponse).
- Reducers: Updating State Reducers listen for dispatched actions and update the state accordingly. Below is a reducer for managing password changes:
// PasswordChangeSlice.js
import {createSlice} from '@reduxjs/toolkit';
const passwordChangeSlice = createSlice({
name: 'passwordChange',
initialState: {
data: null,
isChangeSuccess: null,
error: null,
message: null,
},
reducers: {
changePassword: state => {
state.isChangeSuccess = null;
state.error = null;
state.message = '';
},
changePasswordSuccess: (state, action) => {
state.data = action.payload;
state.message = 'Password changed successfully';
state.isChangeSuccess = true;
},
changePasswordFail: (state, action) => {
state.error = action.payload;
state.message = 'Something went wrong';
state.isChangeSuccess = false;
},
},
});
export const {changePassword, changePasswordSuccess, changePasswordFail} =
passwordChangeSlice.actions;
export default passwordChangeSlice.reducer;
This reducer has three actions:
changePassword: Resets the state when a request starts.
changePasswordSuccess: Updates the state with success data.
changePasswordFail: Updates the state with error information.
- Sagas: Managing Side Effects Now comes the fun part! Sagas handle the actual API calls and dispatch actions based on the response. Here's a saga for the "Change Password" API:
import {all, call, put, takeEvery} from 'redux-saga/effects';
import API from '../Services/ChangePasswordService';
import {
changePasswordFail,
changePasswordSuccess,
} from '../Reducers/PasswordChangeSlice';
function* changePasswordSaga({payload}) {
try {
const response = yield call(API.ChangePassword, payload);
if (response?.data) {
yield put(changePasswordSuccess(response.data));
} else if (response?.errors) {
yield put(changePasswordFail(response.errors));
}
} catch (error) {
yield put(changePasswordFail(error.message));
}
}
function* passwordSaga() {
yield all([
takeEvery('passwordChange/changePassword', changePasswordSaga),
]);
}
export default passwordSaga;
call: Invokes the API function (API.ChangePassword) and waits for the result.
put: Dispatches an action to update the state.
takeEvery: Watches for a specific action (changePassword) and triggers the corresponding worker saga (changePasswordSaga).
If the API call is successful, the saga dispatches changePasswordSuccess; if it fails, it dispatches changePasswordFail.
- Hooks: Connecting Components To trigger the flow, we create a custom hook that dispatches the changePassword action:
// useChangePasswordActions.js
import {useDispatch, useSelector} from 'react-redux';
import {changePassword} from '../Reducers/PasswordChangeSlice';
export const useChangePasswordActions = () => {
const dispatch = useDispatch();
const passwordState = useSelector(state => state?.changePassword);
const changePasswordCall = params => {
dispatch(changePassword(params));
};
return {
passwordState,
changePasswordCall,
};
};
How It All Works Together
Action Dispatch: A component calls changePasswordCall from the custom hook.
Reducer: The changePassword action updates the state to indicate a pending request.
Saga: The watcher saga listens for changePassword and triggers the worker saga.
API Call: The worker saga calls the ChangePassword API and handles the response.
State Update: The saga dispatches success or failure actions, updating the state via the reducer.
Final Thoughts
This setup organizes your API workflow into clear, manageable steps:
Services handle API logic.
Reducers update the state.
Sagas handle side effects and glue everything together.
Remember, this is just one way to structure your project. If you have more experience or ideas, feel free to comment with your strategies. I’ll cover more about sagas in future articles as I learn and improve.
Happy coding! 😊
Top comments (0)