React useState 钩子在 useEffect 钩子内没有按预期工作

时间:2021-03-22 02:41:09

标签: reactjs react-hooks react-typescript

免责声明:请不要将此标记为重复。我看过类似的问题和答案。但他们都没有为我工作。我只是在学习 React。

我想要实现的基本上是无限滚动。这样当用户滚动到页面末尾时,就会加载更多数据。

我使用 scroll eventListener 来实现这一点。它正在工作。

但是我面临着变量状态的问题。

首先,我已将 loading 状态更改为 true。然后获取数据并将状态设置为false

第二,当滚动到页面末尾时,我再次将 loading 状态更改为 true。用 pageNo 添加 1。然后再次获取数据并将 loading 状态设置为 false

问题在于:

  1. loading 状态以某种方式保持 true
  2. 更改 pageNo 状态不起作用。 pageNo 始终保持为 1
  3. 实际上,没有一个州按预期工作。

我的目标:(顺序)

  1. loading 设置为 true

  2. 在组件初始化后从 API 中获取 10 个帖子。

  3. loading 设置为 false

  4. 在用户滚动页面末尾后,用 pageNo 添加 1。

  5. 重复第 1 步到第 3 步,直到所有帖子都加载完毕。

  6. 从 API 获得空响应后,将 allPostsLoaded 设置为 true

我尝试过的:

我尝试将所有状态添加到 useEffect 钩子的 dependencyList 数组中。但随后发生了无限循环。

我也试过只向数组添加 pageNoloading 状态,但发生了同样的无限循环。

来源:

import React, { lazy, useState } from 'react';
import { PostSection } from './Home.styles';
import { BlogPost } from '../../models/BlogPost';
import { PostService } from '../../services/PostService';

const defaultPosts: BlogPost[] = [{
    Id: 'asdfg',
    Content: 'Hi, this is demo content',
    Title: 'Demo title',
    sections: [],
    subTitle: '',
    ReadTime: 1,
    CreatedDate: new Date()
}];

const defaultPageNo = 1;
const PostCardComponent = lazy(() => import('./../PostCard/PostCard'));
const postService = new PostService();

const Home = (props: any) => {
    const [posts, setPosts]: [BlogPost[], (posts: BlogPost[]) => void] = useState(defaultPosts);
    const [pageNo, setPageNo] = useState(defaultPageNo);
    const [pageSize, setPageSize] = useState(10);
    const [loading, setLoading] = useState(false);
    const [allPostsLoaded, setAllPostsLoaded] = useState(false);
    const [featuredPost, setFeaturedPost]: [BlogPost, (featuredPost: BlogPost) => void] = useState(defaultPosts[0]);

    async function getPosts() {
        return await postService.getPosts(pageSize, pageNo);
    }

    async function getFeaturedPost() {
        return await postService.getFeaturedPost();
    }

    function handleScroll(event: any) {
        console.log('loading ' + loading);
        console.log('allPostsLoaded ' + allPostsLoaded);
        var target = event.target.scrollingElement;
        if (!loading && !allPostsLoaded && target.scrollTop + target.clientHeight === target.scrollHeight) {
            setLoading(true);
            setPageNo(pageNo => pageNo + 1);
            setTimeout(()=>{
                getPosts()
                    .then(response => {
                        const newPosts = response.data.data;
                        setLoading(false);
                        if (newPosts.length) {
                            const temp = [ ...posts ];
                            newPosts.forEach(post => !temp.map(m => m.Id).includes(post.Id) ? temp.push(post) : null);
                            setPosts(temp);
                        } else {
                            setAllPostsLoaded(true);
                        }
                    })
            }, 1000);
        }
    }

    function init() {
        setLoading(true);
        Promise.all([getFeaturedPost(), getPosts()])
            .then(
                responses => {
                    setLoading(false);
                    setFeaturedPost(responses[0].data.data);
                    setPosts(responses[1].data.data);
                }
            );
    }

    React.useEffect(() => {
        window.addEventListener("scroll", handleScroll);
        init();
        return () => {
            window.removeEventListener("scroll", handleScroll);
        };
      }, []
    );
    
    return (
        <PostSection className="px-3 py-5 p-md-5">
            <div className="container">
                <div className="item mb-5">
                    {posts.map(post => (
                        <PostCardComponent
                            key={post.Id}
                            Title={post.Title}
                            intro={post.Content}
                            Id={post.Id}
                            ReadTime={post.ReadTime}
                            CreatedDate={post.CreatedDate}
                        />
                    ))}
                </div>
            </div>
        </PostSection>
    );
};

export default Home;

1 个答案:

答案 0 :(得分:0)

使用更多效果来处理 pageNoloaderallPostsLoaded 状态的变化对我有用。

更新来源:

import React, { lazy, useState } from 'react';
import { Guid } from "guid-typescript";
import { PostSection } from './Home.styles';
import { BlogPost } from '../../models/BlogPost';
import { PostService } from '../../services/PostService';
import { Skeleton } from 'antd';

const defaultPosts: BlogPost[] = [{
    Id: '456858568568568',
    Content: 'Hi, this is demo content. There could have been much more content.',
    Title: 'This is a demo title',
    sections: [],
    subTitle: '',
    ReadTime: 1,
    CreatedDate: new Date()
}];
const defaultPageNo = 1;
const defaultPageSize = 10;
const PostCardComponent = lazy(() => import('./../PostCard/PostCard'));
const postService = new PostService();

const Home: React.FC<any> = props => {
    const [posts, setPosts]: [BlogPost[], (posts: BlogPost[]) => void] = useState(defaultPosts);
    const [pageNo, setPageNo] = useState(defaultPageNo);
    const [pageSize, setPageSize] = useState(defaultPageSize);
    const [loading, setLoading] = useState(false);
    const [allPostsLoaded, setAllPostsLoaded] = useState(false);
    const [featuredPost, setFeaturedPost]: [BlogPost, (featuredPost: BlogPost) => void] = useState(defaultPosts[0]);

    function getNewGuid() {
        return Guid.create().toString();
    }

    async function getPosts() {
        return await postService.getPosts(pageSize, pageNo);
    }

    async function getFeaturedPost() {
        return await postService.getFeaturedPost();
    }

    function init() {
        setLoading(true);
        Promise.all([getFeaturedPost(), getPosts()])
            .then(
                responses => {
                    setLoading(false);
                    setFeaturedPost(responses[0].data.data);
                    setPosts(responses[1].data.data);
                }
            );
    }

    React.useEffect(() => {
        init();
        return;
    }, []);

    React.useEffect(() => {
        if (allPostsLoaded || loading) return;

        function handleScroll(event: any) {
            var target = event.target.scrollingElement;
            if (!loading && !allPostsLoaded && target.scrollTop + target.clientHeight === target.scrollHeight) {
                setPageNo(pageNo => pageNo+1);
            }
        }

        window.addEventListener("scroll", handleScroll);
        return () => {
            window.removeEventListener("scroll", handleScroll);
        };
      }, [loading, allPostsLoaded]
    );

    React.useEffect(() => {
        if (pageNo > 1) {
            setLoading(true);
            setTimeout(()=>{
                getPosts()
                    .then(response => {
                        const newPosts = response.data.data;
                        setTimeout(()=>{
                            setLoading(false);
                            if (newPosts.length) {
                                const temp = [ ...posts ];
                                newPosts.forEach(post => !temp.map(m => m.Id).includes(post.Id) ? temp.push(post) : null);
                                setPosts(temp);
                            } else {
                                setAllPostsLoaded(true);
                            }
                        }, 1000);
                    })
            }, 1000);
        }
      }, [pageNo]
    );

    return (
        <PostSection className="px-3 py-5 p-md-5">
            <div className="container">
                <div className="item mb-5">
                    {posts.map(post => (
                        <PostCardComponent
                            key={post.Id}
                            Title={post.Title}
                            intro={post.Content}
                            Id={post.Id}
                            ReadTime={post.ReadTime}
                            CreatedDate={post.CreatedDate}
                        />
                    ))}
                </div>
            </div>
        </PostSection>
    );
};

export default Home;