React — InfiniteScroll (реагирующий бесконечный скроллер), функция loadMore повторно отображает весь список

#reactjs #redux #infinite-scroll

#reactjs #сокращение #бесконечная прокрутка

Вопрос:

Я реализовал бесконечную прокрутку для одного из моих компонентов. Структура кода выглядит следующим образом

У меня вызван родительский компонент, этот компонент выполняет вызов выборки для списка статей. Ниже приведен код

 import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import Row from 'muicss/lib/react/row'
import ReactCSSTransitionGroup from 'react-addons-css-transition-group'

// actions
import { fetchArticles } from '../../../actions/KmActions'
import { setAppHeader } from "../../../actions";

// components
// import KmSubNav from './kmSubNav'
import ArticleList from './articleList'
import Spinner from '../../../components/Reusable/Spinner'
import AuthorsModal from './modal'

const ReactGA = require("react-ga");
ReactGA.initialize("UA-119424435-1");
//prod settings
// ReactGA.initialize("UA-119458679-1");


class KnowledgeMain extends Component {
  constructor(props) {
    super(props)
    this.state = {
      kmLink: ''
    }
  }

  static propTypes = {
    userProfile: PropTypes.object.isRequired,
    setAppHeader: PropTypes.func.isRequired,
    fetchArticles: PropTypes.func.isRequired
  }

  componentDidMount() {
    const { setAppHeader, userProfile, fetchArticles } = this.props
    const { articleTabType } = this.state
    setAppHeader('Knowledge')
    const kmLink = this.props.userProfile.getIn(['homePage', 'links', 'km-service-link'])
    ReactGA.set({
      checkProtocolTask: null,
      checkStorageTask: null,

      userId: this.props.userProfile.getIn(['details', 'id'])
    });
    ReactGA.pageview('Knowledge Management');
    // End Analytics

    if (kmLink) {
      this.setState({ kmLink })
      fetchArticles(`${kmLink}v3/${articleTabType}`)
    }
  }
  componentDidUpdate(prevProps) {
    const { userProfile, fetchArticles } = this.props
    const { userProfile: prevUserProfile } = prevProps
    const toJSUserProfile = userProfile ? userProfile.toJS() : null
    const toJSPrevUserProfile = userProfile ? prevUserProfile.toJS() : null
    if (toJSUserProfile) {
      const prevBaseurl = toJSPrevUserProfile.homePage.links ? toJSPrevUserProfile.homePage.links['km-service-link'] : null
      const baseurl = toJSUserProfile.homePage.links ? toJSUserProfile.homePage.links['km-service-link'] : null
      const { articleTabType } = this.state
      if (baseurl amp;amp; baseurl !== prevBaseurl) {
        this.setState({ kmLink: baseurl })
        fetchArticles(`${baseurl}v3/${articleTabType}`)
      }
    }
  }


  render() {
    const { artieclesLoading } = this.props
    if (artieclesLoading) return <Spinner />
    return (
      <ReactCSSTransitionGroup
        transitionName="phub"
        transitionEnterTimeout={2000}
        transitionLeaveTimeout={2000}
        transitionAppear={true}
        transitionAppearTimeout={500}
      >
        <div className="container">
            <ArticleList
            />
        </div>
      </ReactCSSTransitionGroup>
    )
  }
}

const mapStateToProps = state => ({
  userProfile: state.get('userProfile'),
  artieclesLoading: state.get('km').get('articlesLoading')
})

const mapDispatchToProps = dispatch => {
  let storage = window.localStorage
  return {
    setAppHeader: header => dispatch(setAppHeader(header)),
    fetchArticles: url => dispatch(fetchArticles({ storage, url }))
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(KnowledgeMain)  

В моем дочернем компоненте я использую результат вызова выборки из родительского компонента. Ниже приведен дочерний компонент, показывающий список, для которого я реализовал бесконечную прокрутку с использованием «react-infinite-scroller»

 import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import Row from 'muicss/lib/react/row'
import { hashHistory } from 'react-router'
import InfiniteScroll from "react-infinite-scroller"
import ReactLoading from "react-loading"
import { blue800 } from "material-ui/styles/colors"

// actions
import { updateArticle, readLaterArticle, fetchArticles } from '../../../actions/KmActions'

// components
import ArticleCard from './ArticleCard'

// helpers
import { parseUrl } from '../../../utilities/helpers'

class ArticleListComponent extends Component {
  constructor(props) {
    super(props)
    this.titleClick = this.titleClick.bind(this)
  }

  static propTypes = {
    articles: PropTypes.object.isRequired,
    pagination: PropTypes.object.isRequired
  }

  titleClick(e, article) {
    const link_type = article.get('attributes').get('link_type')
    const id = article.get('id')
    const source_url = article.get('attributes').get('source_url')
    const detail = article.get('links').get('detail')
    if (link_type === 'External') {
      e.preventDefault();
      window.open(source_url, "_blank", "hidden=no")
      // window.open(source_url, "_blank")
      e.preventDefault();

    }
    if (link_type === 'Internal') {
      hashHistory.push({
        pathname: `/km/article/${id}`
      })
    }
  }



  render() {
    const { articles, pagination, loadMore, articlesLoading, onShowMoreClick } = this.props
    return (
      <InfiniteScroll
        pageStart={0}
        initialLoad={false}
        loadMore={e => {
          if (articlesLoading) return
          loadMore(pagination.get("next"))
        }}
        hasMore={pagination.get("next")}
        loader={
          <div style={{ display: "flex", justifyContent: "center" }}>
            <ReactLoading type="bubbles" color={blue800} height={50} width={50} />
          </div>
        }
      >
        <div className="container">
          <div className="o-page-wrapper-km">
            <Row>
              {
                articles amp;amp; articles.size === 0
                  ? (
                    <div style={{ display: 'flex', justifyContent: 'center' }}>
                      <h2>No Articles to Show as of now</h2>
                    </div>
                  )
                  : (
                    <div>
                      {
                        articles.map((article, index) => (
                          <ArticleCard
                            key={index}
                            article={article}
                            titleClick={this.titleClick}
                            tags={article.get('attributes').get('taxonomy_list').split(',')}
                            mediaType={parseUrl(article.get('attributes').get('media_url'))}
                            handleLikeClick={this.props.likeArticle}
                            handleDislikeClick={this.props.dislikeArticle}
                            handleReadLaterClick={this.props.readLaterArticle}
                            handleUndoReadLaterClick={this.props.undoReadLaterArticle}
                            onShowMoreClick={onShowMoreClick}
                          />
                        ))
                      }
                    </div>
                  )
              }
            </Row>
          </div>
        </div>
      </InfiniteScroll>
    )
  }
}

const mapStateToProps = state => ({
  articles: state.get('km').get('articles'),
  pagination: state.get('km').get('pagination'),
  articlesLoading: state.get('km').get('articlesLoading')
})

const mapDispatchToProps = dispatch => {
  let storage = window.localStorage
  return {
    likeArticle: link => dispatch(updateArticle({ storage, link, method: 'POST', type: 'like' })),
    dislikeArticle: link => dispatch(updateArticle({ storage, link, method: 'DELETE', type: 'dislike' })),
    readLaterArticle: link => dispatch(readLaterArticle({ storage, link, method: 'POST', type: 'read_later' })),
    undoReadLaterArticle: link => dispatch(readLaterArticle({ storage, link, method: 'DELETE', type: 'undo_read_later' })),
    loadMore: url => dispatch(fetchArticles({ storage, url, type: 'update' }))
  }
}

const ArticleList = connect(mapStateToProps, mapDispatchToProps)(ArticleListComponent)

export default ArticleList  

Также ниже приведены редукторы, которые я использую для обновления моего магазина redux

Этот способ предназначен для установки списка статей при первом вызове fetch

 case Actions.KNOW_ARTICLES_RES:
      const articles = action.articles || []
      newState = state
        .set('articlesLoading', false)
        .set('articles', fromJS(articles))
        .set('pagination', fromJS(action.pagination))
      return newState  

Это для обновления списка статей новыми данными, которые получены при загрузке с помощью действия бесконечной прокрутки

 case Actions.KNOW_UPDATE_ARTICLES_RES: {
      const articles = action.articles || []
      const loadedArticles = state.hasIn([
        "articles"
      ])
        ? state.get("articles")
        : List()
      newState = state
        .set('articlesLoading', false)
        .set('articles', loadedArticles.concat(fromJS(articles)))
        .set('pagination', fromJS(action.pagination))
      return newState
    }  

Итак, вот проблема, с которой я сталкиваюсь, как только я выполняю вызов выборки в дочернем компоненте при выполнении действия loadMore моего компонента InfiniteScroll, родительский компонент также повторно отображает, из-за повторного отображения всего списка, и это не похоже на действие прокрутки, скорее это выглядит как обновление страницы каждый раз. Чего мне здесь не хватает, что родительский компонент повторно отображает??

Ответ №1:

Извините, но я понял, что я здесь делал неправильно, я использовал одно и то же состояние загрузки как в дочернем, так и в родительском компоненте, и я повторно инициализировал состояние загрузки каждый раз, когда выполнял вызов выборки, из-за чего родительский компонент повторно отображал, потому что в дочернем компоненте InfiniteScroll вызывает функцию load more, которая повторно инициализирует загрузку статей, которую я также использую в родительском компоненте для отображения счетчика и, следовательно, родительский компонент повторно визуализируется при изменении его реквизитов.