【发布时间】:2020-01-30 16:06:51
【问题描述】:
我正在尝试创建一个高阶组件,该组件在从服务器获取数据时显示微调器,并在完成时显示带有数据的组件。然而,通过我在下面完成的实现,它直接渲染组件而不是微调器给出错误can not read property length of null。
我认为这个问题可能与我使用isFetching: false 属性赋予reducer 的初始状态有关。在调试应用程序时,HOC withSpinner 会得到一个 isLoading 属性作为 false。为了测试withSpinner HOC,我将isFetching 初始状态设置为true。这样它只会显示一个微调器,不会继续更新组件。来自 reducer 的选定值 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;
我正在使用 reselect 从我的 redux 存储中获取 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);
还有带有Spinner的高阶组件:
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;
【问题讨论】:
标签: reactjs redux react-redux