【问题标题】:How to show a loading spinner for async requests in a React + Redux application?如何在 React + Redux 应用程序中显示异步请求的加载微调器?
【发布时间】: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


    【解决方案1】:

    正如我所注意到的,您在默认状态下分配 products = null,这不是一个很好的做法(恕我直言)。为什么不首先分配products = [] 来消除错误?使用PropTypes 来确保产品始终是正确的类型也可能很有用...

    【讨论】:

      猜你喜欢
      • 2016-07-23
      • 1970-01-01
      • 2019-10-17
      • 2017-01-26
      • 2022-01-13
      • 2016-11-06
      • 1970-01-01
      • 2020-03-16
      • 1970-01-01
      相关资源
      最近更新 更多