Redux-Saga is a middleware library used to allow a Redux store to asynchronously interact with resources outside of itself. Redux-Saga helps in: making HTTP requests. accessing browser storage.
First install Redux Saga:
npm install redux-saga
Then update your store
to include thunk to your middleware like so:
import { compose, createStore, applyMiddleware } from "redux";
import { persistStore, persistReducer } from "redux-persist";
import storage from "redux-persist/lib/storage";
import logger from "redux-logger";
import createSagaMiddleware from 'redux-saga';
import { rootSaga } from "./root-saga";
import { rootReducer } from "./root-reducer";
const persistConfig = {
key: "root",
storage,
whitelist: ["cart"],
};
const sagaMiddleware = createSagaMiddleware();
const persistedReducer = persistReducer(persistConfig, rootReducer);
const middleWares = [
process.env.NODE_ENV !== "production" && logger,
sagaMiddleware
].filter(Boolean);
const composedEnhancer =
(process.env.NODE_ENV !== "production" &&
window &&
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) ||
compose;
const composedEnhancers = composedEnhancer(applyMiddleware(...middleWares));
export const store = createStore(
persistedReducer,
undefined,
composedEnhancers
);
sagaMiddleware.run(rootSaga);
export const persistor = persistStore(store);
Note on Sagas and Generator functions
Sagas are implemented as Generator functions that yield objects to the redux-saga middleware. The yielded objects are a kind of instruction to be interpreted by the middleware. When a Promise is yielded to the middleware, the middleware will suspend the Saga until the Promise completes.
Here is an example of how a generator function works:
Create a file called category.saga.js
and add your actions and asynchronous function:
import { takeLatest, call, put, all } from "redux-saga/effects";
import { getCategoriesAndDocuments } from "../../utils/firebase/firebase.utils";
import CATEGORIES_ACTION_TYPES from "./categories.types";
import {
fetchCategoriesSuccess,
fetchCategoriesFailed,
} from "./categories.action";
export function* fetchCategoriesAsync() {
try {
const categoriesArray = yield call(getCategoriesAndDocuments, "categories");
yield put(fetchCategoriesSuccess(categoriesArray));
} catch (error) {
yield put(fetchCategoriesFailed(error));
}
}
export function* onFetchCategories() {
yield takeLatest(
CATEGORIES_ACTION_TYPES.FETCH_CATEGORIES_START,
fetchCategoriesAsync
);
}
export function* categoriesSaga() {
yield all([call(onFetchCategories)]);
}
Note: The function call
is used to get back asynchronous code, while put
replaces the dispatch function.
Create a root-saga.js
file and add this code to it:
import {all, call} from 'redux-saga/effects';
import { categoriesSaga } from './categories/category.saga';
export function* rootSaga() {
yield all([call(categoriesSaga)]);
}
Now you dispatch fetchCategoriesStart
inside our component shop
:
import { useEffect } from "react";
import { Routes, Route } from "react-router-dom";
import { useDispatch } from "react-redux";
import CategoriesPreview from "../categories-preview/categories-preview.component";
import Category from "../category/category.component";
import { fetchCategoriesStart } from "../../store/categories/categories.action";
const Shop = () => {
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchCategoriesStart());
}, []);
return (
<Routes>
<Route index element={<CategoriesPreview />}></Route>
<Route path=":category" element={<Category />}></Route>
</Routes>
);
};
export default Shop;