To do this best practice, use the following method:
I used some packages and patterns for best practice:
- redux-logger for log actions and states in console of browser.
- reselect Selectors can compute derived data, allowing Redux to
store the minimal possible state and etc.
- redux-thunk Thunks are the recommended middleware for basic
Redux side effects logic, including complex synchronous logic that
needs access to the store, and simple async logic like AJAX requests
and etc.
- axios for work with api (Promise based HTTP client for the
browser and node.js)
create a directory by name redux or any name of you like in src folder and
then create two files store.js
and rootReducer.js
in redux directory. We assume fetch products from API.
To do this:
Create a new directory by name product in redux directory and then
create four files by names product.types.js, product.actions.js,
product.reducer.js, product.selector.js
in redux/product
directory
The structure of the project should be as follows
...
src
App.js
redux
product
product.types.js
product.actions.js
product.reducer.js
rootReducer.js
store.js
Index.js
package.json
...
store.js
In this file we do the redux configuration
// redux/store.js:
import { createStore, applyMiddleware } from "redux";
import logger from "redux-logger";
import thunk from "redux-thunk";
import rootReducer from "./root-reducer";
const middlewares = [logger, thunk];
export const store = createStore(rootReducer, applyMiddleware(...middlewares));
rootReducer.js
The combineReducers
helper function turns an object whose values are
different reducing functions into a single reducing function you can
pass to createStore
.
// redux/rootReducer.js
import { combineReducers } from "redux";
import productReducer from "./product/product.reducer";
const rootReducer = combineReducers({
shop: productReducer,
});
export default rootReducer;
product.types.js
In this file we define constants for manage types of actions.
export const ShopActionTypes = {
FETCH_PRODUCTS_START: "FETCH_PRODUCTS_START",
FETCH_PRODUCTS_SUCCESS: "FETCH_PRODUCTS_SUCCESS",
FETCH_PRODUCTS_FAILURE: "FETCH_PRODUCTS_FAILURE"
};
product.actions.js
In this file we create action creators for handle actions.
// redux/product/product.actions.js
import { ShopActionTypes } from "./product.types";
import axios from "axios";
export const fetchProductsStart = () => ({
type: ShopActionTypes.FETCH_PRODUCTS_START
});
export const fetchProductsSuccess = products => ({
type: ShopActionTypes.FETCH_PRODUCTS_SUCCESS,
payload: products
});
export const fetchProductsFailure = error => ({
type: ShopActionTypes.FETCH_PRODUCTS_FAILURE,
payload: error
});
export const fetchProductsStartAsync = () => {
return dispatch => {
dispatch(fetchProductsStart());
axios
.get(url)
.then(response => dispatch(fetchProductsSuccess(response.data.data)))
.catch(error => dispatch(fetchProductsFailure(error)));
};
};
product.reducer.js
In this file we create productReducer
function for handle actions.
import { ShopActionTypes } from "./product.types";
const INITIAL_STATE = {
products: [],
isFetching: false,
errorMessage: undefined,
};
const productReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case ShopActionTypes.FETCH_PRODUCTS_START:
return {
...state,
isFetching: true
};
case ShopActionTypes.FETCH_PRODUCTS_SUCCESS:
return {
...state,
products: action.payload,
isFetching: false
};
case ShopActionTypes.FETCH_PRODUCTS_FAILURE:
return {
...state,
isFetching: false,
errorMessage: action.payload
};
default:
return state;
}
};
export default productReducer;
product.selector.js
In this file we select products
and isFetching
from shop state.
import { createSelector } from "reselect";
const selectShop = state => state.shop;
export const selectProducts = createSelector(
[selectShop],
shop => shop.products
);
export const selectIsProductsFetching = createSelector(
[selectShop],
shop => shop.isFetching
);
Index.js
In this file wrapped whole app and components with Provider
for access child components to the store and states.
// src/Index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { Provider } from "react-redux";
import { store } from "./redux/store";
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
App.js class component
In this file we do connect to the store and states with class component
// src/App.js
import React, { Component } from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import {
selectIsProductsFetching,
selectProducts
} from "./redux/product/product.selectors";
import { fetchProductsStartAsync } from "./redux/product/product.actions";
class App extends Component {
componentDidMount() {
const { fetchProductsStartAsync } = this.props;
fetchProductsStartAsync();
}
render() {
const { products, isProductsFetching } = this.props;
console.log('products', products);
console.log('isProductsFetching', isProductsFetching);
return (
<div className="App">Please see console in browser</div>
);
}
}
const mapStateToProps = createStructuredSelector({
products: selectProducts,
isProductsFetching: selectIsProductsFetching,
});
const mapDispatchToProps = dispatch => ({
fetchProductsStartAsync: () => dispatch(fetchProductsStartAsync())
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);
or App.js with functional component ( useEffect hook )
In this file we do connect to the store and states with functional component
// src/App.js
import React, { Component, useEffect } from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import {
selectIsProductsFetching,
selectProducts
} from "./redux/product/product.selectors";
import { fetchProductsStartAsync } from "./redux/product/product.actions";
const App = ({ fetchProductsStartAsync, products, isProductsFetching}) => {
useEffect(() => {
fetchProductsStartAsync();
},[]);
console.log('products', products);
console.log('isProductsFetching', isProductsFetching);
return (
<div className="App">Please see console in browser</div>
);
}
const mapStateToProps = createStructuredSelector({
products: selectProducts,
isProductsFetching: selectIsProductsFetching,
});
const mapDispatchToProps = dispatch => ({
fetchProductsStartAsync: () => dispatch(fetchProductsStartAsync())
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);