1.Create a Redux Store:
// store.js
import { createStore, applyMiddleware, combineReducers } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import thunk from 'redux-thunk'; // Middleware for async actions
import rootReducer from './reducers'; // Import your root reducer
// Combine your reducers
const rootReducer = combineReducers({
// your individual reducers here
});
const store = createStore(
rootReducer,
composeWithDevTools(applyMiddleware(thunk))
);
export default store;
2. Create Redux Reducers:
// reducers.js
import { combineReducers } from 'redux';
// Create individual reducers
const exampleReducer = (state = initialState, action) => {
switch (action.type) {
// Handle actions and update state
default:
return state;
}
};
const rootReducer = combineReducers({
example: exampleReducer,
// Add more reducers here
});
export default rootReducer;
3. Connect Redux to React Components:
// SomeComponent.js
import { connect } from 'react-redux';
const SomeComponent = ({ someData }) => {
return (
<div>
<p>{someData}</p>
</div>
);
};
const mapStateToProps = (state) => ({
someData: state.example.someData, // Use the actual state path
});
export default connect(mapStateToProps)(SomeComponent);
4.Dispatch Actions:
// SomeComponent.js
import { useDispatch } from 'react-redux';
const SomeComponent = () => {
const dispatch = useDispatch();
const updateData = () => {
dispatch({ type: 'UPDATE_DATA', payload: 'New Data' });
};
return (
<div>
<button onClick={updateData}>Update Data</button>
</div>
);
};
2. Example With Saga
Use Redux ‘Saga’ for handling data fetching and dispatching actions, you’ll need to create a saga that handles the asynchronous operation.
1.dataSlice
// dataSlice.js
import { createSlice } from '@reduxjs/toolkit';
const dataSlice = createSlice({
name: 'data',
initialState: null,
reducers: {
setData: (state, action) => {
return action.payload;
},
setError: (state, action) => {
// Handle error action if needed
},
// Define the 'fetchData' action
fetchData: (state) => {
// This action can be used to trigger data fetching in your saga
},
},
});
//const { actions, reducer } = dataSlice;
//export const { setData, setError, fetchData } = actions;
//export default reducer;
//Another Way to export
export const { setData, setError, fetchData } = dataSlice.actions;
export default dataSlice.reducer;
2. // sagas/dataSaga.js
import { takeLatest, call, put } from 'redux-saga/effects';
import axios from 'axios'; // You can use any HTTP library
import { setData, setError } from '../features/dataSlice'; // Import your actions
// Worker Saga: Handles the data fetching operation
function* fetchDataSaga() {
try {
const response = yield call(axios.get, 'https://api.example.com/data');
const result = response.data;
// Dispatch the 'setData' action with the fetched data
yield put(setData(result));
} catch (error) {
// Dispatch the 'setError' action if an error occurs
yield put(setError(error.message)); // Dispatch an error action if fetching fails
}
}
// Watcher Saga: Listens for the 'FETCH_DATA' action
export function* watchFetchData() {
yield takeLatest('FETCH_DATA', fetchDataSaga);
}
In Redux Saga, the connection between the action and the saga is established through the ‘takeLatest’ effect in the watcher saga. The action type(‘FETCH_DATA’) that you specify in takeLatest should match the action type that you dispatch ‘dispatch(fetchData())’in your component.
4.// components/Home.js
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { fetchData } from '../features/dataSlice'; // Import the action
function Home() {
const dispatch = useDispatch();
const data = useSelector((state) => state.data);
useEffect(() => {
// Dispatch the FETCH_DATA action to trigger the saga
dispatch(fetchData());
}, []);
return (
<div>
<h1>Home Page</h1>
{data && <p>Data: {data}</p>}
</div>
);
}
export default Home;
When the component mounts, it dispatches the FETCH_DATA action, which is intercepted by the saga, performs the data fetching, and updates the Redux store with the result. Any errors during fetching are also handled gracefully.
5. Root Saga Configuration
// sagas/index.js
import { all } from 'redux-saga/effects';
import { watchFetchData } from './dataSaga'; // Import your saga
export default function* rootSaga() {
yield all([watchFetchData()]);
}
6.Redux Store Configuration with Saga and redux toolkit
// store.js
import { combineReducers, configureStore } from "@reduxjs/toolkit";
import createSagaMiddleware from 'redux-saga';
import logger from 'redux-logger'
import dataReducer from './dataSlice';
import rootSaga from './sagas'; // Import your root saga
const rootReducer = combineReducers({
dataState: dataReducer
})
const sagaMiddleware = createSagaMiddleware();
const store = configureStore({
reducer: rootReducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(sagaMiddleware),
});
sagaMiddleware.run(rootSaga); // Run the root saga
export default store;
NOTE :
In Redux Toolkit, you don’t necessarily need to create action types and action creators separately when you’re using slices. Redux Toolkit provides a convenient way to define actions and reducers together in a single slice. Action types and action creators are automatically generated for you.
Dependency Array: The dependency array is used to specify when the effect should run.
if you have a component that depends on a specific piece of state, you can include that state variable in the dependency array. When that state variable changes, the effect will run again.
By using useEffect with a dependency array, you control when the effect should run, which is essential for ensuring that certain actions are taken at the right time during the component’s lifecycle.
Example with a dependency array:
useEffect(() => {
// 1.This effect runs when the component mounts and
// 2. whenever 'someValue' changes
fetchData(someValue);
}, [someValue]);
NOTE 2 : when you include a Redux store value or selector in the dependency array of a useEffect hook, the effect will run whenever that store value changes. This is a common approach for syncing your component with changes in the Redux store.
Example :
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { someSelector, someAction } from './redux/slice';
function MyComponent() {
const dispatch = useDispatch();
const valueFromStore = useSelector(someSelector);
useEffect(() => {
// This effect runs whenever 'valueFromStore' changes in the Redux store
// You can perform actions based on the new value
console.log('Value from store changed:', valueFromStore);
// Using 'dispatch' without including it in the dependency array is safe
dispatch(someAction());
}, [valueFromStore]); // Only include relevant dependencies
return (
<div>
<p>Value from store: {valueFromStore}</p>
<button onClick={() => dispatch(someAction())}>Update Value</button>
</div>
);
}
export default MyComponent;
Whenever the value of valueFromStore in the Redux store changes, the effect will run.
When you click the “Update Value” button, it dispatches an action (someAction()), and if that action modifies valueFromStore in the Redux store, the effect will be triggered because the dependency value has changed.
To avoid these issues, it’s generally recommended not to include dispatch in the dependency array. Instead, you can safely use dispatch within the effect without listing it as a dependency. This is because dispatch doesn’t change during the component’s lifetime, so there’s no need to re-run the effect when it changes.