Я пытаюсь создать компонент более высокого порядка, который отображает счетчик при получении данных с сервера и компонент с данными, когда это будет сделано. Однако, с реализацией, которую я сделал ниже, она идет прямо к рендерингу компонента, а не счетчику, выдающему ошибку it can not read property length of null
.
Я думаю, что проблема может быть связана с начальным состоянием, которое я даю редуктору со свойством isFetching: false
. При отладке приложения HOC withSpinner
получает isLoading
prop как false
. Для проверки withSpinner
HOC я установил начальное состояние isFetching
как true
. Таким образом, он отображает только счетчик и не продолжает обновлять компонент. Выбранное значение isFetching
из редуктора никогда не обновляется до false
.
Файл действий Redux
import ProductActionTypes from "./product.types";
import { DB } from "../../api/DB";
const fetchProductsStart = () => ({
type: ProductActionTypes.FETCH_PRODUCTS_START
});
const fetchProductsSuccess = products => ({
type: ProductActionTypes.FETCH_PRODUCTS_SUCCESS,
payload: products
});
const fetchProductsFailure = errorMessage => ({
type: ProductActionTypes.FETCH_PRODUCTS_FAILURE,
payload: errorMessage
});
export const fetchProductsStartAsync = (series, queryStrings) => {
return dispatch => {
dispatch(fetchProductsStart());
DB.Product.getFilteredProducts(series, queryStrings)
.then(products => dispatch(fetchProductsSuccess(products)))
.catch(err => dispatch(fetchProductsFailure(err)));
};
};
Редуктор Redux
import ProductActionTypes from "./product.types";
const INITIAL_STATE = {
products: null,
errorMessage: null,
isFetching: false,
totalResultsFromQuery: 0
};
const productReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case ProductActionTypes.FETCH_PRODUCTS_START:
return {
...state,
isFetching: true
};
case ProductActionTypes.FETCH_PRODUCTS_SUCCESS:
return {
...state,
isFetching: false,
products: action.payload[0],
totalResultsFromQuery: action.payload[1][0].totalResultsFromQuery
};
case ProductActionTypes.FETCH_PRODUCTS_FAILURE:
return {
...state,
isFetching: false,
errorMessage: action.payload
};
default:
return state;
}
};
export default productReducer;
Я использую перевыбор вполучить значение isFetching из моего магазина приставок:
export const selectIsFetching = createSelector(
[selectProducts],
products => products.isFetching
);
Компонент React:
import React from "react";
import { connect } from "react-redux";
import { withRouter } from "react-router-dom";
import { compose } from "redux";
import { fetchProductsStartAsync } from "../../redux/product/product.actions";
import {
selectProductItems,
selectIsFetching,
} from "../../redux/product/product.selectors";
import {
selectCurrentPage,
selectCapacity
} from "../../redux/filter/filter.selectors";
import withSpinner from "components/withSpinner/withSpinner";
import ProductItemCard from "components/ProductItemCard/ProductItemCard";
import "./ProductsWrapper.css";
import {
selectSearchInput,
selectColor,
selectFamily,
selectPriceFrom,
selectPriceTo
} from "redux/filter/filter.selectors";
import { resetDefault } from "../../redux/filter/filter.actions";
class ProductsWrapper extends React.Component {
componentDidMount() {
const {
fetchProducts,
match: { params },
color,
searchInput,
priceFrom,
priceTo,
family,
page,
capacity
} = this.props;
fetchProducts(params.seria, {
color,
searchInput,
priceFrom,
priceTo,
family,
page,
capacity
});
}
componentDidUpdate(prevProps) {
const {
fetchProducts,
match: { params },
color,
searchInput,
priceFrom,
priceTo,
family,
resetFilterToDefault,
page,
capacity
} = this.props;
if (params.seria !== prevProps.match.params.seria) {
resetFilterToDefault();
fetchProducts(params.seria, {
color,
searchInput,
priceFrom,
priceTo,
family,
page,
capacity
});
}
if (
color !== prevProps.color ||
searchInput !== prevProps.searchInput ||
priceFrom !== prevProps.priceFrom ||
priceTo !== prevProps.priceTo ||
family !== prevProps.family ||
page !== prevProps.page ||
capacity !== prevProps.capacity
) {
fetchProducts(params.seria, {
color,
searchInput,
priceFrom,
priceTo,
family,
page,
capacity
});
}
}
render() {
const { products } = this.props;
return (
<div className="products-outer-wrapper">
<div className="products-wrapper">
{products.length !== 0 ? (
products.map(({ ...productProps }, index) => (
<ProductItemCard {...productProps} key={index} />
))
) : (
<p>No products!</p>
)}
</div>
</div>
);
}
}
const mapStateToProps = state => ({
products: selectProductItems(state),
isLoading: selectIsFetching(state),
color: selectColor(state),
searchInput: selectSearchInput(state),
family: selectFamily(state),
priceFrom: selectPriceFrom(state),
priceTo: selectPriceTo(state),
page: selectCurrentPage(state),
capacity: selectCapacity(state)
});
const mapDispatchToProps = dispatch => ({
fetchProducts: (series, queryStrings) =>
dispatch(fetchProductsStartAsync(series, queryStrings)),
resetFilterToDefault: () => dispatch(resetDefault())
});
export default compose(
connect(
mapStateToProps,
mapDispatchToProps
),
withRouter,
withSpinner
)(ProductsWrapper);
И компонент высшего порядка с помощью Sppinner:
import React from "react";
import Spinner from "../Spinner/Spinner";
const withSpinner = WrappedComponent => {
const enhancedComponent = ({ isLoading, ...otherProps }) => {
return isLoading ? <Spinner /> : <WrappedComponent {...otherProps} />;
};
return enhancedComponent;
};
export default withSpinner;