Search code examples
reactjsreact-reduxinfinite-scroll

Infinite Scrolling not working in the react when api is called


I'm using react - redux environment for my web app and I'm not able to make an inifinite scroll, Once i reach the bottom, I'm getting an error that useEffect cannot be used. Here is the code below:

import React, { useEffect } from 'react';
import './Homefeed.css';
import StickyNav from '../StickyNav/StickyNav';
import { fetchHomePosts } from '../Redux/HomeFeed-Redux/HomeFeedActionMethods';
import { connect } from 'react-redux';
import PostCell from '../PostCell/PostCell';
import InfiniteScroll from 'react-infinite-scroll-component';
import { useDispatch } from 'react-redux'



function Homefeed({homeposts,fetchHomePosts}) {

    var skipper = 0;
    
    let dispatch = useDispatch()

    useEffect(()=>{fetchHomePosts(skipper);},[dispatch]);
  
    
   

    function MorePosts(){
        console.log(homeposts);
        console.log("more");
        useEffect(()=>{fetchHomePosts(skipper+10);},[]);
  
    }
    

    return homeposts.isLoading ? (<h2>Loading...</h2>) : homeposts.error ? (<h3>{homeposts.error}</h3>) : (


            <div className="homefeed-layout">
                <title>Home Feed</title>
                <StickyNav />
                <InfiniteScroll
                    dataLength={homeposts.homeposts.length}
                    hasMore={true}
                    loader={<h3>Loading</h3>}
                    next={()=>{MorePosts()}}                                                             
                    >
                    {
                        homeposts.homeposts?.map((key,index)=> {
                            

                            var slug = key.attributes.title;
                                slug = slug.replace(/\s/g, '-');
                                slug = slug.toLowerCase();
                                slug = slug.replace(/[^a-zA-Z0-9\-]/g, '');
                                slug = slug.replace(/(-)\1+/g, '-');
                                slug = slug.replace(/-$/, '');
                                slug = slug.replace(/^-/, '');

                            return( 
                                    <div className="content-area" key={index} >
                                        
                                        <PostCell
                                             key ={index}
                                             slug = {slug}
                                             postId = {key.id}
                                             title ={key.attributes.title}
                                             likes ={key.attributes.likes}
                                             comments ={key.attributes.comments}
                                             category ={key.attributes.category}
                                             postimageurl = {key.attributes.postImageURL}
                                             handle ={key.attributes.createdBy.attributes.handle}
                                             timestamp = {key.attributes.createdAt}
                                             subtitle = {key.attributes.subtitle}
                                             realName ={key.attributes.createdBy.attributes.realName}
                                             profileImage = {key.attributes.createdBy.attributes.profileImage?._url}
                                             followers = {key.attributes.createdBy.attributes.followers}
                                             posts = {key.attributes.createdBy.attributes.posts}
                                             name = {key.attributes.createdBy.attributes.name}
                                        />
                                        

                                    </div>
                            )
                        })
                    }
                </InfiniteScroll>


            </div>
            
            
            
        )
}

const mapStatetoProps = (state) =>{
    return{
        homeposts:state.HomePosts
    }
}

const mapDispatchtoProps = (dispatch) =>{
    return{
        fetchHomePosts:(skipper)=>{dispatch(fetchHomePosts(skipper))},dispatch,
    }
}

export default connect(mapStatetoProps,mapDispatchtoProps) (Homefeed)

So here the next prop in the Infinite scroll is supposed to call more data which should append to the existing data. The moreposts function should call the api but react gives me an error saying that you cannot call useEffect inside this function.

If i use useDispatch(), its stuck in an infinite loop, can someone please help me out, I'm pretty new to this.


Solution

  • You cannot call useEffect inside of another function because this breaks the rules of hooks. But the useEffect isn't doing anything here. You could just call fetchHomePosts(skipper+10) from morePosts().

    That would load the second page. If you want to load the third and the fourth and so on then you need to make skipper a state rather than a var. Add 10 to skipper whenever you load a page.

    You can either:

    • Increment skipper and call fetchHomePosts() in your morePosts() function.
    • Increment skipper in your morePosts() function and call fetchHomePosts() from a useEffect hook which has skipper as a dependency so that the effect runs whenever the value of skipper changes.
    • Save the last fetched page number in your Redux store.
    • Base the offset on the length of the homePosts array.

    You don't need to use useDispatch and connect. They are two ways of doing the same thing so you should use one or the other. The hooks are the recommended approach.

    This is not perfect but I don't want to overcomplicate things:

    export default function Homefeed() {
      const homeposts = useSelector((state) => state.HomePosts);
      const dispatch = useDispatch();
    
      // want to make sure that the first page is loaded when the component mounts
      useEffect(() => {
        dispatch(fetchHomePosts(0));
      }, [dispatch]);
    
      // starts at 0 and increases when more posts load
      const dataLength = homeposts.homeposts.length;
    
      // function to load the next page
      const morePosts = () => {
        dispatch(fetchHomePosts(dataLength));
      };
    
      return homeposts.isLoading ? (
        <h2>Loading...</h2>
      ) : homeposts.error ? (
        <h3>{homeposts.error}</h3>
      ) : (
        <div className="homefeed-layout">
          <title>Home Feed</title>
          <StickyNav />
          <InfiniteScroll
            dataLength={dataLength}
            hasMore={true}
            loader={<h3>Loading</h3>}
            next={morePosts}
          >
            {homeposts.homeposts?.map((key, index) => {
              // I would store this slug in Redux when you store the post
              const slug = key.attributes.title
                .replace(/\s/g, "-")
                .toLowerCase()
                .replace(/[^a-zA-Z0-9-]/g, "")
                .replace(/(-)\1+/g, "-")
                .replace(/-$/, "")
                .replace(/^-/, "");
    
              return (
                <div className="content-area" key={index}>
                  <PostCell
                    key={index}
                    slug={slug}
                    postId={key.id}
                    title={key.attributes.title}
                    likes={key.attributes.likes}
                    comments={key.attributes.comments}
                    category={key.attributes.category}
                    postimageurl={key.attributes.postImageURL}
                    handle={key.attributes.createdBy.attributes.handle}
                    timestamp={key.attributes.createdAt}
                    subtitle={key.attributes.subtitle}
                    realName={key.attributes.createdBy.attributes.realName}
                    profileImage={
                      key.attributes.createdBy.attributes.profileImage?._url
                    }
                    followers={key.attributes.createdBy.attributes.followers}
                    posts={key.attributes.createdBy.attributes.posts}
                    name={key.attributes.createdBy.attributes.name}
                  />
                </div>
              );
            })}
          </InfiniteScroll>
        </div>
      );
    }