阵列完成渲染后反应渲染组件

React render component after array finished rendering

我正在对 componentDidMount 进行 api 调用,问题是 api 需要几秒钟才能响应,同时用户可以选择转到另一个页面 这样做会发出另一个请求,如果发生这种情况,应用程序会崩溃。如何解决这个问题?为了解决这个问题,我只需要在呈现所有数组时才呈现分页组件我该怎么做?

import React, { Component, Fragment } from "react";
import Episode from "../components/Episode";

import "react-virtualized/styles.css"; // only needs to be imported once
import { withStyles } from "@material-ui/core/styles";
import Typography from "@material-ui/core/Typography";
import Grid from "@material-ui/core/Grid";
import Paper from "@material-ui/core/Paper";
import ButtonBase from "@material-ui/core/ButtonBase";
import CircularProgress from "@material-ui/core/CircularProgress";
import Like from "@material-ui/icons/ThumbUp";
import IconButton from "@material-ui/core/IconButton";
import NextButton from "@material-ui/icons/NavigateNext";
import PreviousButton from "@material-ui/icons/NavigateBefore";

// This example assumes you have a way to know/load this information

const styles = theme => ({
 //styles
});

class SeriesPage extends Component {
  constructor(props) {
    super(props);

    this.state = {
      apiToken: "",
      serie: {
        image: "",
        description: "",
        title: "",
        likes: 0,
        type: "",
        apiName: ""
      },
      startEpisode: 1,
      endEpisode: 10,
      episodes: [],
      loaded: false,
      clicked: false,
      enabled: true
    };
  }

  componentDidMount() {
    this.initialize(this.state.startEpisode, this.state.endEpisode);
  }

  initialize = async (startIndex, stopIndex) => {
    await this.getTokenFromApi();
    await this.getSerieDetailsByApiName();
    await this.getEpisodeBySeriesApiNameWithRange(startIndex, stopIndex);
  };

  getTokenFromApi = async () => {
    const data = {
      name: "generateToken",
      param: {
        email: "*",
        pass: "*"
      }
    };
    return fetch("*", {
      method: "post",
      headers: {
        Accept: "application/json",
        "Content-Type": "application/json"
      },
      body: JSON.stringify(data)
    })
      .then(response => {
        if (!response.ok) {
          this.setState({
            episodes: "Network request failed"
          });
          throw Error("Network request failed");
        }
        return response;
      })
      .then(res => {
        return res.json();
      })
      .then(content => {
        if (content.response.status === 200) {
          this.setState({
            apiToken: content.response.result.token
          });
        }
      })
      .catch(error => {
        this.setState({
          episodes: "There was an internal error"
        });
        throw error;
      });
  };

  getSerieDetailsByApiName = async () => {
    const data = {
      name: "*",
      param: {
        serieApiName: this.props.match.params.series
      }
    };
    return fetch("*", {
      method: "post",
      headers: {
        Authorization: "Bearer " + this.state.apiToken,
        "Content-Type": "application/json"
      },
      body: JSON.stringify(data)
    })
      .then(response => {
        if (!response.ok) {
          this.setState({
            episodes: "Network request failed"
          });
          throw Error("Network request failed");
        }
        return response;
      })
      .then(response => {
        return response.json(); //response.json() is resolving its promise. It waits for the body to load
      })
      .then(responseData => {
        if (responseData.response.status === 200) {
          this.setState(
            {
              serie: responseData.response.result,
              loaded: true
            },
            () => {
              console.log(this.state);
            }
          );
        }
      })
      .catch(error => {
        this.setState({
          episodes: "There was an internal error"
        });
        throw error;
      });
  };

  getEpisodeBySeriesApiNameWithRange = async (startIndex, stopIndex) => {
    const data = {
      name: "*",
      param: {
        serieApiName: this.props.match.params.series,
        startIndex: startIndex,
        stopIndex: stopIndex
      }
    };
    return fetch("*", {
      method: "post",
      headers: {
        Authorization: "Bearer " + this.state.apiToken,
        "Content-Type": "application/json"
      },
      body: JSON.stringify(data)
    })
      .then(response => {
        if (!response.ok) {
          this.setState({
            episodes: "Network request failed"
          });
          throw Error("Network request failed");
        }
        return response;
      })
      .then(response => {
        return response.json(); //response.json() is resolving its promise. It waits for the body to load
      })
      .then(responseData => {
        if (responseData.response.status === 200) {
          this.setState(prevState => ({
            episodes: [...prevState.episodes, ...responseData.response.result]
          }));
        }
      })
      .catch(error => {
        this.setState({
          episodes: "There was an internal error"
        });
      });
  };

  handleLikeClick = () => {
    if (this.state.clicked) {
      this.setState(
        prevState => ({
          clicked: !prevState.clicked,
          serie: {
            ...prevState.serie,
            likes: Number(prevState.serie.likes) - 1
          }
        }),
        () => {
          const data = {
            name: "removeLikeSerie",
            param: {
              serieApiName: this.state.serie.apiName
            }
          };
          return fetch("*", {
            method: "post",
            headers: {
              Authorization: "Bearer " + this.state.apiToken,
              "Content-Type": "application/json"
            },
            body: JSON.stringify(data)
          })
            .then(response => {
              if (!response.ok) {
                this.setState({
                  episodes: "Network request failed"
                });
                throw Error("Network request failed");
              }
              return response;
            })
            .catch(error => {
              this.setState({
                episodes: "There was an internal error"
              });
            });
        }
      );
    } else {
      this.setState(
        prevState => ({
          clicked: !prevState.clicked,
          serie: {
            ...prevState.serie,
            likes: Number(prevState.serie.likes) + 1
          }
        }),
        () => {
          const data = {
            name: "likeSerie",
            param: {
              serieApiName: this.state.serie.apiName
            }
          };
          return fetch("*", {
            method: "post",
            headers: {
              Authorization: "Bearer " + this.state.apiToken,
              "Content-Type": "application/json"
            },
            body: JSON.stringify(data)
          })
            .then(response => {
              if (!response.ok) {
                this.setState({
                  episodes: "Network request failed"
                });
                throw Error("Network request failed");
              }
              return response;
            })
            .catch(error => {
              this.setState({
                episodes: "There was an internal error"
              });
            });
        }
      );
    }
  };

  previousPage = () => {
    if (this.state.startEpisode === 11) {
      this.setState(
        prevState => ({
          episodes: [],
          startEpisode: prevState.startEpisode - 10,
          endEpisode: prevState.endEpisode - 10,
          enabled: true
        }),
        () => {
          this.initialize(this.state.startEpisode, this.state.endEpisode);
        }
      );
    } else if (this.state.startEpisode > 10) {
      this.setState(
        prevState => ({
          episodes: [],
          startEpisode: prevState.startEpisode - 10,
          endEpisode: prevState.endEpisode - 10
        }),
        () => {
          this.initialize(this.state.startEpisode, this.state.endEpisode);
        }
      );
    }
  };

  nextPage = () => {
    this.setState(
      prevState => ({
        episodes: [],
        startEpisode: prevState.startEpisode + 10,
        endEpisode: prevState.endEpisode + 10,
        enabled: false
      }),
      () => {
        this.initialize(this.state.startEpisode, this.state.endEpisode);
      }
    );
  };

  renderRow = item => {
    const { classes, headerIsHidden, ...other } = this.props;
    return <Episode key={item.videoId} episode={item} {...other} />;
  };

  // Render your list
  render() {
    const { classes } = this.props;

    return (
      <Fragment>
        <div className={classes.serieDetails}>
          {this.state.loaded ? (
            <Paper className={classes.root}>
              <Grid container spacing={16}>
                <Grid item>
                  <ButtonBase className={classes.image}>
                    <img
                      className={classes.img}
                      alt={this.state.serie.title + " Image"}
                      src={this.state.serie.image}
                    />
                  </ButtonBase>
                </Grid>
                <Grid item xs={12} sm container>
                  <Grid item xs container direction="column" spacing={16}>
                    <Grid item xs>
                      <Typography gutterBottom variant="subtitle1">
                        {this.state.serie.title}
                      </Typography>
                      <Typography gutterBottom>
                        {this.state.serie.description}
                      </Typography>
                      <Typography color="textSecondary">
                        <IconButton
                          className={classes.button}
                          className={this.state.clicked ? classes.liked : ""}
                          aria-label="Like this serie"
                          onClick={this.handleLikeClick}
                        >
                          <Like />
                        </IconButton>
                        {this.state.serie.likes}
                      </Typography>
                    </Grid>
                  </Grid>
                </Grid>
              </Grid>
            </Paper>
          ) : (
            ""
          )}
        </div>
        <div className={classes.content}>
          <div className={classes.innerContent}>
            {this.state.episodes.constructor === String ? (
              this.state.episodes
            ) : (
              <div>
                {this.state.episodes.map(this.renderRow)}
                <div className={classes.pagination}>
                  <IconButton
                    aria-label="Previous"
                    className={classes.button}
                    onClick={this.previousPage}
                    disabled={this.state.enabled}
                  >
                    <PreviousButton />
                  </IconButton>
                  <IconButton
                    aria-label="Next"
                    className={classes.button}
                    onClick={this.nextPage}
                  >
                    <NextButton />
                  </IconButton>
                </div>
              </div>
            )}
          </div>
        </div>
      </Fragment>
    );
  }
}
export default withStyles(styles, { withTheme: true })(SeriesPage);

分页组件是 div 和 className={classes.pagination} 数组存储在 state

向您的状态添加一个名为 loading 的变量,该变量最初为真:

state = {
      ...,
      loading: true
}

在 getEpisodeBySeriesApiNameWithRange 的响应 return 之后,您可以将加载状态设置为 false:

 getEpisodeBySeriesApiNameWithRange = async (startIndex, stopIndex) => {
    const data = {
      name: "*",
      param: {
        serieApiName: this.props.match.params.series,
        startIndex: startIndex,
        stopIndex: stopIndex
      }
    };
    return fetch("*", {
      method: "post",
      headers: {
        Authorization: "Bearer " + this.state.apiToken,
        "Content-Type": "application/json"
      },
      body: JSON.stringify(data)
    })
      .then(response => {
        if (!response.ok) {
          this.setState({
            episodes: "Network request failed",
            loading: false
          });
          throw Error("Network request failed");
        }
        return response;
      })
      .then(response => {
        return response.json(); //response.json() is resolving its promise. It waits for the body to load
      })
      .then(responseData => {
        if (responseData.response.status === 200) {
          this.setState(prevState => ({
            episodes: [...prevState.episodes, ...responseData.response.result],
            loading: false
          }));
        }
      })
      .catch(error => {
        this.setState({
          episodes: "There was an internal error",
          loading: false
        });
      });
  };

当您点击下一页或上一页时,再次设置加载状态为真:

 nextPage = () => {
this.setState(
     prevState => ({
        episodes: [],
        startEpisode: prevState.startEpisode + 10,
        endEpisode: prevState.endEpisode + 10,
        enabled: false,
        loading: true
      }),
      () => {
        this.initialize(this.state.startEpisode, this.state.endEpisode);
      }
    );
  };

上一页相同

render 中,您将仅在加载为 false 时渲染组件,这意味着已获取数据:

{this.state.loading ? null : <div className={classes.content}>
      <div className={classes.innerContent}>
        {this.state.episodes.constructor === String ? (
          this.state.episodes
        ) : (
          <div>
            {this.state.episodes.map(this.renderRow)}
            <div className={classes.pagination}>
              <IconButton
                aria-label="Previous"
                className={classes.button}
                onClick={this.previousPage}
                disabled={this.state.enabled}
              >
                <PreviousButton />
              </IconButton>
              <IconButton
                aria-label="Next"
                className={classes.button}
                onClick={this.nextPage}
              >
                <NextButton />
              </IconButton>
            </div>
          </div>
        )}
      </div>
    </div>
   }