React Infinite Scroll 超出最大更新深度

Maximum update depth exceeded in React Infinite Scroll

我正在尝试像 producthunt 一样在 MERN 堆栈应用程序中实现延迟加载。我希望默认显示在当前日期创建的 post。如果用户向下滚动,它将在前一个日期获取更多数据。我正在使用反应无限滚动。但是,似乎应用程序请求 api 就像一个无限循环,而不是在滚动时监听。我收到以下错误。 Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.

这个函数是async/await所以我不明白为什么老请求还没有解决却一直调用新请求

在Post个组件中

import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import Spinner from '../layout/Spinner';
import PostItem from './PostItem';
import UserItem from '../users/UserItem';
import TopDiscussion from '../TopDiscussion';
import SmallAbout from '../SmallAbout';
import { getPostsByDate } from '../../actions/post';
import Moment from 'react-moment';
import InfiniteScroll from 'react-infinite-scroller';

const Posts = ({ getPostsByDate, post: { posts, loading } }) => {
  const now = new Date();
  const startOfToday = new Date(
    now.getFullYear(),
    now.getMonth(),
    now.getDate()
  );

  // startOfToday =  startOfToday -1
  useEffect(() => {
    getPostsByDate(startOfToday);
  }, [getPostsByDate]);

  const [date, setDate] = useState(startOfToday);
  const [shown, setShown] = useState();

  const getPosts = () => {
    getPostsByDate(date);
    let count = new Date(date);
    count.setDate(count.getDate() - 1);
    setDate(count);
  };

  return loading ? (
    <Spinner />
  ) : (
    <div className='main-grid'>
      <div className='posts-grid'>
        <h1 className='large text-primary'>Ideas</h1>
        <div className='posts'>
          <div className='post-dummy'>
            <InfiniteScroll
              dataLength={posts.length}
              pageStart={0}
              loadMore={getPosts}
              hasMore={posts && posts.length < 10}
              loader={
                <div className='loader' key={0}>
                  Loading ...
                </div>
              }
            >
              {posts
                .sort((a, b) =>
                  a.likes.length > b.likes.length
                    ? -1
                    : b.likes.length > a.likes.length
                    ? 1
                    : 0
                )
                .map(post => (
                  <PostItem key={post._id} post={post} />
                ))}
            </InfiniteScroll>
          </div>
        </div>
      </div>
      <div className='right-panel-grid'>
        <SmallAbout />
        <UserItem />
        <TopDiscussion posts={posts} />
        <div
          className='fb-group'
          data-href='https://www.facebook.com/groups/ideatoshare/'
          data-width='350'
          data-show-social-context='true'
          data-show-metadata='false'
        ></div>

        <iframe
          title='producthunt'
          style={{ border: 'none' }}
          src='https://cards.producthunt.com/cards/posts/168618?v=1'
          width='350'
          height='405'
          frameBorder='0'
          scrolling='no'
          allowFullScreen
        ></iframe>
      </div>
    </div>
  );
};

Posts.propTypes = {
  getPostsByDate: PropTypes.func.isRequired,
  post: PropTypes.object.isRequired
};

const mapStateToProps = state => ({
  post: state.post
});
export default connect(
  mapStateToProps,
  { getPostsByDate }
)(Posts);

Post减速器

import {
  GET_POSTS,
  POST_ERROR,
  UPDATE_LIKES,
  UPDATE_LIKE,
  UPDATE_COMMENT_LIKES,
  DELETE_POST,
  ADD_POST,
  GET_POST,
  ADD_COMMENT,
  REMOVE_COMMENT,
  ADD_SUB_COMMENT,
  REMOVE_SUB_COMMENT,
  UPDATE_STATUS
} from '../actions/types';

const initialState = {
  posts: [],
  post: null,
  loading: true,
  error: {}
};

export default function(state = initialState, action) {
  const { type, payload } = action;

  switch (type) {
    case GET_POSTS:
      return {
        ...state,
        posts: [...state.posts, ...payload],
        // posts: payload,
        loading: false
      };
    case GET_POST:
      return {
        ...state,
        post: payload,
        loading: false
      };
    case ADD_POST:
      return {
        ...state,
        post: payload,
        // posts: [payload, ...state.posts],
        loading: false
      };

    case POST_ERROR:
      return {
        ...state,
        error: payload,
        loading: false
      };

    case UPDATE_COMMENT_LIKES:
      return {
        ...state,
        post: {
          ...state.post,
          comments: payload
        },
        loading: false
      };

    case UPDATE_LIKES:
      return {
        ...state,
        posts: state.posts.map(post =>
          post._id === payload.id ? { ...post, likes: payload.likes } : post
        ),
        loading: false
      };
    case UPDATE_LIKE:
      return {
        ...state,
        post: { ...state.post, likes: payload },
        loading: false
      };

    case UPDATE_STATUS:
      return {
        ...state,
        posts: state.posts.map(post =>
          post._id === payload.id ? { ...post, status: payload.status } : post
        ),
        loading: false
      };

    case DELETE_POST:
      return {
        ...state,
        posts: state.posts.filter(post => post._id !== payload),
        loading: false
      };
    case ADD_COMMENT:
      return {
        ...state,
        // payload is all the comments
        post: { ...state.post, comments: payload },
        loading: false
      };

    case ADD_SUB_COMMENT:
      return {
        ...state,
        // payload is all the comments of a post
        post: { ...state.post, comments: payload },
        loading: false
      };

    case REMOVE_COMMENT:
      return {
        ...state,
        post: {
          ...state.post,
          comments: state.post.comments.filter(
            comment => comment._id !== payload
          ),
          loading: false
        }
      };

    case REMOVE_SUB_COMMENT:
      return {
        ...state,
        post: {
          ...state.post,
          comments: payload
          // comments: state.post.comments.map(comment =>
          //   {
          //   if (comment._id === payload.commentId) {
          //     comment.subComments.filter(
          //       subcomment => subcomment._id === payload.subcommentId
          //     );
          //   }
          // }
          // )
        },
        loading: false
      };

    default:
      return state;
  }
}

Post 动作

//GetTodayPost
export const getPostsByDate = date => async dispatch => {
  try {
    const res = await axios.get(`/api/posts/${date}`);
    dispatch({
      type: GET_POSTS,
      payload: res.data
    });
  } catch (err) {
    dispatch({
      type: POST_ERROR,
      payload: { msg: err.response.statusText, status: err.response.status }
    });
  }
};

post API

router.get('/:date', async (req, res) => {
  try {

    const startOfToday = new Date(req.params.date);
    const endOfToday = new Date(req.params.date);
    endOfToday.setDate(endOfToday.getDate() + 1);
    const posts = await Post.find({
      date: { $gte: startOfToday, $lte: endOfToday }
    }).sort({
      date: -1
    });
    res.json(posts);
  } catch (err) {
    console.error(err.message);
    res.send(500).send('Server Error');
  }
});

编辑: 我已经用 working example 更新了你的仓库。你的问题是你的 API 不是 'unlimited',因为你声称,你 do 实际上需要检查是否所有帖子都已加载。使用我随更新提供的示例我对你的仓库做了,你应该能从这里弄明白。

好的.. 所以在使用 InfiniteScroll 进行一些测试后,这似乎正在发生,因为你的 hasMore 属性 总是等于 true... 你必须指定某种类型的条件,以便 InfiniteScroll 知道何时加载更多数据,何时不加载。

我在添加检查之前遇到了与您相同的错误,它告诉 InfiniteScroll 没有更多数据要加载。

我构建了以下示例来展示如何使用 InfiniteScroll

You can view a live demo here


PostsContainer.js

import React, { useState, useEffect } from "react";
import Posts from "./Posts";
import InfiniteScroll from "react-infinite-scroller";

const loadingStyle = {
  textAlign: "center",
  fontSize: "48px",
  color: "red"
};

function PostsContainer({ url, itemsToDisplay = 5 }) {
  const [data, setData] = useState();
  const [shownData, setShownData] = useState();

  useEffect(() => {
    (async () => {
      let items = await fetchPosts(url);
      let itemsToShow = selectNItems(items, itemsToDisplay);
      setShownData(itemsToShow);
      setData(items);
    })();
  }, [url]);

  async function fetchPosts(url) {
    let res = await fetch(url);
    return await res.json();
  }

  const selectNItems = (obj, n) => {
    return obj.slice(0, n);
  }

  const loadMorePosts = () => {
    let items =
      data &&
      shownData && 
      selectNItems(data, shownData.length + itemsToDisplay)
    setShownData(items);
  };

  return (
    <InfiniteScroll
      pageStart={0}
      loadMore={loadMorePosts}
      hasMore={data && shownData && data.length > shownData.length}
      loader={<div style={loadingStyle}>Loading ...</div>}
      useWindow={true}
    >
      <Posts posts={shownData} />
    </InfiniteScroll>
  );
}

export default PostsContainer;

Posts.js

import React from 'react';
import Post from './Post';

const headingStyle = {
  textAlign: 'center',
}

function Posts({ posts }) {
  return(
    <div>
      <h1 style={headingStyle}>Posts</h1>
      {posts && posts.length > 0 && posts.map((p, i) => <Post key={i} data={p} index={i} />)}
    </div>
  );
}

export default Posts;

Post.js

import React from "react";

const containerStyle = {
  border: "1px solid black",
  margin: "10px auto",
  maxWidth: "50vw",
  padding: '0px 10px 0px 0px'
};

const postHeaderStyle = {
  textAlign: "center",
  padding: "0px"
};

function Post({ data, index }) {
  return (
    <div style={containerStyle}>
      {index !== "" && <h3 style={postHeaderStyle}>Post #{index}</h3>}
      <ul>
        <li>
          <b>userId:</b> {data.userId}
        </li>
        <li>
          <b>id:</b> {data.id}
        </li>
        <li>
          <b>title:</b> {data.title}
        </li>
        <li>
          <b>body:</b> {data.body}
        </li>
      </ul>
    </div>
  );
}

export default Post;

index.js

import React from "react";
import { render } from "react-dom";
import PostsContainer from "./Components/PostsContainer";

function App() {
  return (
    <PostsContainer
      itemsToDisplay={5}
      url="https://jsonplaceholder.typicode.com/posts"
    />
  );
}

render(<App />, document.getElementById("root"));