用于无限滚动的 React JS 地图数组

React JS map array for infinite scroll

我有一个简单的 React 应用程序,我在其中获取 Flickr Public Feed API 并显示它。不幸的是,它多次映射阵列,我可以在其中看到重复的照片。请求总是 returns 一个包含 20 个具有相同图片的项目的数组,解释重复。

检查下面的代码:

import React, { Component } from 'react';
import $ from 'jquery';

import PhotoListItem from '../../components/photoListItem';
import Searchbar from '../../components/searchBar';
import ScrollButton from '../../components/scrollButton';

import '../app/index.css';

export default class PhotoApp extends Component {
    constructor(props) {
        super(props);

        this.state = {
            photoList: [],
            searchTerm: 'cyanotype',
            items: 10,
            loadingState: false,
        }
    }

    componentDidMount() {
        this.getPhotoList();
        this.onInfiniteScroll();
    }

    /* get data from Flickr public feed */
    getPhotoList = () => {
        const flickrApiPoint = "https://api.flickr.com/services/feeds/photos_public.gne?jsoncallback=?&tags=" + this.state.searchTerm;

        try {
            $.ajax({
                url: flickrApiPoint,
                dataType: 'jsonp',
                data: { format: "json" },
                success: function (data) {
                    this.setState({ photoList: data.items });
                }.bind(this)
            });
        }
        catch (err) {
            console.log(err);
        }
    }

    /* code for infinite scroll */
    onInfiniteScroll = () => {
        this.refs.iScroll.addEventListener("scroll", () => {
            if (this.refs.iScroll.scrollTop + this.refs.iScroll.clientHeight >= this.refs.iScroll.scrollHeight - 20) {
                this.loadMoreItems();
            }
        });
    }

    /*  */
    displayItems = () => {
        var items = [];
        for (var i = 0; i < this.state.items; i++) {
            items.push(
                this.state.photoList.map((photo, index) => {
                    const author = photo.author.split(/"/)[1];
                    const authorLink = photo.description.split(/"/)[1]
                    const description = photo.description.split(/"/)[13]
                    return (
                        <PhotoListItem
                            key={index}
                            url={photo.media.m}
                            photoLink={photo.link}
                            title={photo.title}
                            author={author}
                            authorLink={authorLink}
                            description={description}
                            tags={photo.tags} />
                    )
                })
            );
        }
        return items;
    }

    /*  */
    loadMoreItems = () => {
        if (this.state.loadingState) {
            return;
        }
        this.setState({ loadingState: true });
        setTimeout(() => {
            this.setState({ items: this.state.items + 10, loadingState: false });
        }, 1000);
    }

    render() {
        return (
            <div className='appContainer' ref="iScroll">
                <div className='appHeader'>
                    <h1 className='headerTitle'>Welcome to Flickr Alternative Photography Feed!</h1>
                </div>

                <div className='gridContainer'>
                    {this.displayItems()}
                </div>
                {this.state.loadingState ? <p className='loading'>Loading items...</p> : ""}
            </div>
        );
    }
}

现场直播EXAMPLE

问题出在 this.displayItems(),但我该如何解决这个问题? 任何帮助表示赞赏。谢谢!

您可以根据要在 JSX 中显示的项目数量对数组进行切片来实现此目的:

this.state.photoList.slice(0, this.state.items).map(

然后您将不得不使用 setState 的回调版本来使用您状态的旧值并增加您想要显示的内容:

this.setState(old => ({ items: old.items + 2, loadingState: false }));

完整功能示例(建议使用 "full page" 选项):

class PhotoListItem extends React.Component {
  render() {
    return (
      <div className="image-card">
        <img className="image-card__image" alt="" src={this.props.url} />
        <div className="image-card__body">
          <div className="image-title">
            <a href={this.props.photoLink}>{this.props.title}</a>
            <span className="image-author">
              {" "}
              by <a href={this.props.authorLink}>{this.props.author}</a>
            </span>
          </div>
          <div className="image-description">
            <span className="description">Description:</span>{" "}
            {this.props.description}
          </div>
          <div className="image-tags">
            <span className="tags">Tags:</span> {this.props.tags}
          </div>
        </div>
      </div>
    );
  }
}

class PhotoApp extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      photoList: [],
      items: 2,
      searchTerm: "cyanotype",
      loadingState: false
    };
  }

  componentDidMount() {
    this.getPhotoList();
    this.onInfiniteScroll();
  }

  /* get data from Flickr public feed */
  getPhotoList = () => {
    const flickrApiPoint =
      "https://api.flickr.com/services/feeds/photos_public.gne?jsoncallback=?&tags=" +
      this.state.searchTerm;

    try {
      $.ajax({
        url: flickrApiPoint,
        dataType: "jsonp",
        data: { format: "json" },
        success: function(data) {
          this.setState({ photoList: data.items });
        }.bind(this)
      });
    } catch (err) {
      console.log(err);
    }
  };

  /* code for infinite scroll */
  onInfiniteScroll = () => {
    this.refs.iScroll.addEventListener("scroll", () => {
      if (
        this.refs.iScroll.scrollTop + this.refs.iScroll.clientHeight >=
        this.refs.iScroll.scrollHeight - 20
      ) {
        this.loadMoreItems();
      }
    });
  };

  /*  */
  loadMoreItems = () => {
    if (this.state.loadingState) {
      return;
    }
    this.setState({ loadingState: true });
    setTimeout(() => {
      this.setState(old => ({ items: old.items + 2, loadingState: false }));
    }, 1000);
    this.getPhotoList();
  };

  render() {
    return (
      <div className="appContainer" ref="iScroll">
        <div className="appHeader">
          <h1 className="headerTitle">
            Welcome to Flickr Alternative Photography Feed!
          </h1>
        </div>

        <div className="gridContainer">
          {this.state.photoList.slice(0, this.state.items).map((photo, index) => {
            const author = photo.author.split(/"/)[1];
            const authorLink = photo.description.split(/"/)[1];
            const description = photo.description.split(/"/)[13];
            return (
              <PhotoListItem
                key={index}
                url={photo.media.m}
                photoLink={photo.link}
                title={photo.title}
                author={author}
                authorLink={authorLink}
                description={description}
                tags={photo.tags}
              />
            );
          })}
        </div>
        {this.state.loadingState ? (
          <p className="loading">Loading items...</p>
        ) : (
          ""
        )}
      </div>
    );
  }
}

ReactDOM.render(<PhotoApp />, document.getElementById("root"));
body,
html {
  margin: 0;
  min-height: 100%;
}

.appContainer {
  font-family: "Helvetica", sans-serif;
  width: 100%;
  height: 100vh;
  overflow: auto;
}

.appHeader {
  text-align: center;
  background-color: #033666;
  padding: 1rem;
}

.headerTitle {
  color: #fff;
}

.gridContainer {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  padding: 1rem;
  grid-gap: 1rem 1rem;
}

.loading {
  text-align: center;
  color: #033666;
}

@media only screen and (max-width: 320px) {
  .appHeader>h1 {
    font-size: 1.2rem;
  }
}

a,
a:visited {
  color: #000;
  text-decoration: none;
}

a:hover {
  color: #033666;
  text-decoration: underline;
}

.image-card {
  display: flex;
  display: -webkit-box;
  display: -moz-box;
  display: -ms-flexbox;
  display: -webkit-flex;
  flex-direction: column;
  width: auto;
  height: auto;
  margin: .5rem;
  border-radius: 5px;
  box-shadow: 0 5px 15px rgba(0, 0, 0, .15);
  background: #fff;
}

.image-card__image {
  border-radius: 5px 5px 0 0;
  width: 100%;
  height: 200px;
  object-fit: cover;
}

.image-card__body {
  padding: .5rem 1rem 1rem;
}

.image-title {
  font-weight: 600;
  margin: 0;
  word-wrap: break-word;
  padding-bottom: .7rem;
  cursor: pointer;
}

.image-author {
  font-weight: 100;
  font-size: .8rem;
  cursor: pointer;
}

.image-owner {
  margin-top: 0;
  font-size: .8rem;
}

.image-date-view-wrapper {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.image-description {
  padding-bottom: .7rem;
  font-size: .9rem;
  word-wrap: break-word;
}

.tags,
.description {
  font-weight: 600;
}

.image-tags {
  font-size: .8rem;
  word-wrap: break-word;
}

.App {
  font-family: sans-serif;
  text-align: center;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.1/umd/react-dom.production.min.js"></script>
<div id="root" />