React Redux Saga
React Redux Saga
React Redux Saga
without an API:
Ex1:-We'll build a simple counter app that increments and decrements a value in Redux
when buttons are clicked.
First, we'll setup the React components. We'll have an App component that renders the
Counter component. The Counter component will display the count value from Redux and
have buttons to increment and decrement the count.
```jsx
// App.js
function App() {
return <Counter />;
}
// Counter.js
Next, we'll setup Redux with an initial state and actions/reducer to handle incrementing
and decrementing the count value.
```js
// store.js
// Initial state
const initialState = {
count: 0
};
// Action types
const INCREMENT = 'INCREMENT';
const DECREMENT = 'DECREMENT';
// Reducer
function reducer(state = initialState, action) {
switch (action.type) {
case INCREMENT:
return { ...state, count: state.count + 1 };
case DECREMENT:
return { ...state, count: state.count - 1 };
default:
return state;
}
}
// Root saga
function* rootSaga() {}
// Action creators
const increment = () => ({type: INCREMENT});
const decrement = () => ({type: DECREMENT});
function Counter() {
// Dispatch actions
const dispatch = useDispatch();
return (
<div>
<h1>Count: {count}</h1>
<button onClick={() => dispatch(increment())}>Increment</button>
<button onClick={() => dispatch(decrement())}>Decrement</button>
</div>
);
}
```
This implements a basic Redux app with React components without needing an API backend.
The count state is managed in Redux and updated when actions are dispatched in response
to button clicks. Redux-Saga is setup but not utilized since we have no async logic needed.
Ex2 .Here is how we could modify the example to integrate with an API backend:
```js
// api.js
We'll use the getCount API method in the Counter component to fetch the initial count:
```jsx
// Counter.js
// ...
useEffect(() => {
getCount().then(count => dispatch({type: 'SET_COUNT', count}));
}, []);
```
We'll use redux-saga to handle the async API call to fetch the count:
```js
// sagas.js
function* fetchCountSaga() {
const count = yield call(getCount);
yield put({type: 'SET_COUNT', count});
}
function* rootSaga() {
yield takeEvery('FETCH_COUNT', fetchCountSaga);
}
```
```js
// Counter.js
useEffect(() => {
dispatch({type: 'FETCH_COUNT'});
}, []);
```
We can call the API to update the count when increment/decrement actions are
dispatched:
```js
// sagas.js
function* incrementSaga() {
yield call(updateCount, count + 1);
yield put({type: 'FETCH_COUNT'});
}
function* decrementSaga() {
// similar to increment
}
function* rootSaga() {
// ...
yield takeEvery('INCREMENT', incrementSaga);
yield takeEvery('DECREMENT', decrementSaga);
}
```
So in summary, we fetch the initial count data from the API, use sagas to handle async
actions, and call the API to update the count when actions are dispatched. The components
listen for state changes to update the UI.
Ex3:-Okay, let's go through another more real-world example in more depth, comparing it
to concepts in JavaScript and React.
Imagine we have an e-commerce app where we need to fetch and display a list of products.
Here's how we could implement it with Redux, React and Redux-Saga:
Redux Store
The Redux store will hold the state of our app - in this case, an array of product objects:
```js
const initialState = {
products: []
};
```
This is similar to having a global state object in vanilla JavaScript that components can
reference.
Actions
We'll need action creators to define the actions that can be dispatched to the store, like
`fetchProducts`:
```js
const fetchProducts = () => ({
type: 'FETCH_PRODUCTS'
});
```
These are like event handler functions in JavaScript. They return an action object rather
than directly modifying state.
# Reducers
```js
function productsReducer(state = [], action) {
switch (action.type) {
case 'FETCH_PRODUCTS_SUCCESS':
return action.products;
default:
return state;
}
}
```
Reducers are like event listeners that update state in response to actions. This is similar
to how event handlers modify state in JavaScript.
# Action Creators
```jsx
// ProductList.js
This is like calling an event handler function in JavaScript when we want to trigger some
behavior.
# Sagas
Sagas listen for actions and handle async logic:
```js
function* fetchProductsSaga() {
// call API
const products = yield call(Api.fetchProducts);
function* rootSaga() {
yield takeEvery('FETCH_PRODUCTS', fetchProductsSaga);
}
```
Sagas are like separate asynchronous event handlers. This keeps synchronous reducers and
action creators purely focused on state updates.
# React Components
```jsx
function ProductList({ products }) {
return (
<div>
{products.map(p => <Product key={p.id} product={p} />)}
</div>
);
}
```
Here is an example of implementing CRUD operations (create, read, update, delete) with
Redux, React and an API, explaining what's happening:
# Read
```jsx
// ItemList.js
useEffect(() => {
// dispatch fetch action
dispatch(fetchItems())
}, []);
// items saga
function* fetchItemsSaga() {
// call API
const items = yield call(API.fetchItems);
function* watchFetchItems() {
yield takeEvery('FETCH_ITEMS', fetchItemsSaga);
}
```
When the component mounts, it dispatches a `FETCH_ITEMS` action. The saga listens for this
and calls the API, then dispatches a `FETCH_ITEMS_SUCCESS` containing the data. The
reducer sets this as the new state.
# Create
// items saga
function* createItemSaga(action) {
// call API
yield call(API.createItem, action.payload);
function* watchCreateItem() {
yield takeEvery('CREATE_ITEM', createItemSaga);
}
```
When the form is submitted, it dispatches a `CREATE_ITEM` action containing the data. The
saga calls the API to create it, then fetches an updated list from the server.
# Update
```jsx
// ItemEdit.js
// items saga
function* updateItemSaga(action) {
// call API
yield call(API.updateItem, action.payload);
function* watchUpdateItem() {
yield takeEvery('UPDATE_ITEM', updateItemSaga);
}
```
Again this dispatches an action that the saga picks up. It calls the API and then refetches the
updated data.
# Delete
To delete an item:
```jsx
// ItemDelete.js
// items saga
function* deleteItemSaga(action) {
// call API
yield call(API.deleteItem, action.payload);
function* watchDeleteItem() {
yield takeEvery('DELETE_ITEM', deleteItemSaga);
}
```
The flow is similar - dispatch a DELETE action, saga calls API and refetches data.
So in summary, components dispatch CRUD actions that sagas pick up and handle by calling
the API, then refetch data to update state. This keeps the data flow unidirectional.
Here is a diagram showing how data flows in a Redux + React application with Sagas:
```mermaid
graph LR
A[React Components]
B[Action Creators]
C[Redux Store]
D[Reducers]
E[Sagas]
F[API]
A-->B
B-->C
C-->D
A-->C
D-->C
E-->F
F-->E
B-->E
E-->B
```
So in summary:
This pattern keeps the data and state changes easy to reason about.
There are some similarities between the architecture of a Redux+React app and a Spring Boot
web application:
- The Redux store is analogous to the model layer in Spring MVC - it represents the state of the
application.
- Redux reducers are similar to repositories/services in Spring - they contain business logic for
modifying state.
- Redux actions are like controllers in Spring MVC - they handle dispatching actions in response
to user input.
- React components are like Spring MVC controllers - they handle rendering UI and call services
to fetch/update data.
- Redux sagas/thunks are like Spring services - they encapsulate business logic and call APIs.
- React context provides dependency injection like Spring - components can access data
without prop drilling.
- Redux follows strict uni-directional data flow, Spring MVC allows bi-directional.
- React abstracts away the HTTP layer, Spring exposes it for control.
So in summary, both leverage separation of concerns with layered architecture and have
similarities in the role each layer plays. But Redux enforces a stricter unidirectional data flow
and React focuses purely on the view layer as a client-side SPA framework.