如何在promise中的componentDidMount中设置setState而不出现setState错误

时间:2018-07-12 05:10:35

标签: javascript reactjs promise fetch

我正在调用一个api,并通过响应设置状态。 我无法调用setState而不看到此错误: 如果setState发生在promise之外,则不会发生该错误。

如何将API响应设置为状态而不看到此错误?

  

无法在未安装的组件上调用setState(或forceUpdate)

componentDidMount() {
    const url = 'https://localhost:8000/api/items'
    let items;
    fetch(url, {mode: 'cors'})
      .then(res => res.json())
      .then(
        (result) => {
          this.setState({
            isLoaded: true,
            items: result
          });
        },
        (error) => {
          this.setState({
            isLoaded: true,
            error
          });
        }
      )
  }

整个组件供参考:

import React, { Component } from 'react'
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles';
import GridList from '@material-ui/core/GridList';
import GridListTile from '@material-ui/core/GridListTile';
import GridListTileBar from '@material-ui/core/GridListTileBar';
import ListSubheader from '@material-ui/core/ListSubheader';
import IconButton from '@material-ui/core/IconButton';
import InfoIcon from '@material-ui/icons/Info';

const styles = theme => ({
  root: {
    display: 'flex',
    flexWrap: 'wrap',
    justifyContent: 'space-around',
    overflow: 'hidden',
    backgroundColor: theme.palette.background.paper,
  },
  icon: {
    color: 'rgba(255, 255, 255, 0.54)',
  },
});

class ItemList extends Component {
  constructor(props) {
    super(props)
    this.state = {
      items: [],
      isLoaded: false,
    }

    this.handleItemClick = this.handleItemClick.bind(this);
  }

  componentDidMount() {
    const url = 'https://localhost:8000/api/items'
    let items;
    fetch(url, {mode: 'cors'})
      .then(res => res.json())
      .then(
        (result) => {
          this.setState({
            isLoaded: true,
            items: result
          });
        },
        (error) => {
          this.setState({
            isLoaded: true,
            error
          });
        }
      )
  }

  handleItemClick(id) {

  }

  handleRenderItems() {
      const { classes } = this.props
      const { items } = this.state;
      return this.state.items.map((item, idx) => {
      const id = item.id;
      return (
        <GridListTile onClick={() => this.handleItemClick(id)} key={idx}>
        <img src={item.key_image} alt={item.title} />
        <GridListTileBar
          title={item.title}
          subtitle={<span>${item.rent_price_day}/day</span>}
          actionIcon={
            <IconButton className={classes.icon}>
              <InfoIcon />
            </IconButton>
          }
        />
        </GridListTile>
      )
    })
  }

  render() {
    const { classes } = this.props;
    return (
      <div className={classes.root}>
        <GridList cols={3} cellHeight={220} className={classes.gridList}>
          <GridListTile key="Subheader" cols={3} style={{ height: 'auto' }}>
            <ListSubheader component="div">Popular Items</ListSubheader>
          </GridListTile>
          {this.handleRenderItems()}
        </GridList>
      </div>
    );
  }
}

ItemList.propTypes = {
  classes: PropTypes.object.isRequired,
};

export default withStyles(styles)(ItemList);

与其在组件中进行处理,不如在操作创建者中处理对API的调用,并使用redux存储数据,然后将其发送到组件(如果可用),可能会更简单。最好还是将这些细节保留在视图之外。

此外,通常也不会在视图中使用样式对象-但这是带有material-ui的样板。

2 个答案:

答案 0 :(得分:3)

在组件内部添加以下内容,

componentDidMount() { 
  this.isMounted = true;
}

componentWillUnmount() {
   this.isMounted = false;
}

仅当this.isMounted为true时,才设置setState。这应该可以解决您的问题,但这不是推荐的模式。请参阅https://reactjs.org/blog/2015/12/16/ismounted-antipattern.html

检查包含<ItemList/>的父组件的代码。如果您根据某些条件渲染<ItemList/>,则问题可能出在这些条件下。

答案 1 :(得分:1)

很简单。您只需要在变量中返回第二个Promise的值即可。 像这样:

  let items; 
  let errorFetch;   
  fetch(url, {mode: 'cors'})
  .then(res => res.json())
  .then(
    (result) => {
      items = result;
    },
    (error) => {
      errorFetch = error
    }
  );
  this.setState(items ? { isLoaded : true, items } : { isLoaded : true, error: errorFetch });