Modular State Management in React with Redux, React-Redux, and Asynchronous Actions via Redux-Thunk
Setting Up the Environment
Install required packages:
npm install redux react-redux redux-thunk --save
Create the store configuration in store/index.js and integrate it into the main entry point src/index.js. Set up a basic home page component with views/home/index.jsx and its corresponding UI layer UI.jsx.
Modular Reducers by Feature
Create Separate Reducer Files per Page
For example, define state logic for the Home page:
views/home/reducer.js
const homeReducer = (state = {
bannerItems: [],
productItems: []
}, action) => {
const { type, payload } = action;
switch (type) {
case 'UPDATE_BANNER_DATA':
return { ...state, bannerItems: payload };
case 'UPDATE_PRODUCT_DATA':
return { ...state, productItems: payload };
default:
return state;
}
};
export default homeReducer;
Repeat similar patterns for other features like kind and cart.
Combine Reducers in Store
store/index.js
import { createStore, combineReducers, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import homeReducer from '@/views/home/reducer';
import kindReducer from '@/views/kind/reducer';
import cartReducer from '@/views/cart/reducer';
const rootReducer = combineReducers({
home: homeReducer,
kind: kindReducer,
cart: cartReducer
});
const store = createStore(rootReducer, applyMiddleware(thunk));
export default store;
Connect Store to Application Root
src/index.js
import { Provider } from 'react-redux';
import store from './store';
import ReactDOM from 'react-dom';
ReactDOM.render(
<Provider store={store}>
<Router>
<Switch>
<Route path="/" component={App} />
</Switch>
</Router>
</Provider>,
document.getElementById('root')
);
Using React-Redux for Component Integration
Split Components in to Containre and Presantational Layers
views/home/index.jsx (Container)
import { connect } from 'react-redux';
import HomeUI from './UI';
const mapStateToProps = (state) => ({
banners: state.home.bannerItems,
products: state.home.productItems
});
export default connect(mapStateToProps)(HomeUI);
views/home/UI.jsx (Presentational)
import React, { Component } from 'react';
export default class HomeUI extends Component {
componentDidMount() {
// Data fetching is handled via actions
}
render() {
const { banners, products } = this.props;
return (
<div className="home-container">
<header className="header">Home Page</header>
<main className="main-content">
{banners.map(item => (
<p key={item.bannerId}>{item.imageUrl}</p>
))}
{products.map(item => (
<p key={item.productId}>{item.productName}</p>
))}
</main>
</div>
);
}
}
Dispatching Actions from Container
views/home/index.jsx (Enhanced)
import { connect } from 'react-redux';
import { getBannerData, getProductData } from '@/utils/api';
const mapDispatchToProps = (dispatch) => ({
fetchBanners() {
getBannerData().then(data =>
dispatch({ type: 'UPDATE_BANNER_DATA', payload: data.results })
);
},
fetchProducts() {
getProductData().then(data =>
dispatch({ type: 'UPDATE_PRODUCT_DATA', payload: data.items })
);
}
});
export default connect(mapStateToProps, mapDispatchToProps)(HomeUI);
Consume State and Actions in UI
views/home/UI.jsx (Updated)
export default class HomeUI extends Component {
componentDidMount() {
this.props.fetchBanners();
this.props.fetchProducts();
}
render() {
const { banners, products } = this.props;
return (
<div className="home-container">
<header className="header">Home Page</header>
<main className="main-content">
{banners.map(item => (
<p key={item.bannerId}>{item.imageUrl}</p>
))}
{products.map(item => (
<p key={item.productId}>{item.productName}</p>
))}
</main>
</div>
);
}
}
Handling Async Logic with Redux-Thunk
Move async operations outside of containers using action creators.
Add Middleware to Store
Update store/index.js to include redux-thunk middleware:
import { createStore, combineReducers, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
// ... reducer imports
const store = createStore(rootReducer, applyMiddleware(thunk));
export default store;
Extract Async Logic into Action Creators
views/home/actionCreators.js
import { getBannerData, getProductData } from '@/utils/api';
export const loadBanners = () => {
return (dispatch) => {
getBannerData().then(response => {
dispatch({
type: 'UPDATE_BANNER_DATA',
payload: response.data.results
});
}).catch(err => console.error(err));
};
};
export const loadProducts = () => {
return (dispatch) => {
getProductData().then(response => {
dispatch({
type: 'UPDATE_PRODUCT_DATA',
payload: response.data.items
});
}).catch(err => console.error(err));
};
};
Use Action Creators in Container
views/home/index.jsx (Final Version)
import { connect } from 'react-redux';
import { loadBanners, loadProducts } from '@/views/home/actionCreators';
const mapDispatchToProps = {
fetchBanners: loadBanners,
fetchProducts: loadProducts
};
export default connect(mapStateToProps, mapDispatchToProps)(HomeUI);
Now the container component triggers asynchronous actions through clean, reusable action creators, improving maintainability and separation of concerns.