Infinite Scroll hooks

This feature is currently in beta version. We want to test it in real projects before releasing the v1, just in case we have to modify the API. If you’re interested in this feature, it’d be great if you could install it, test it, and share your feedback in this Feature Discussion. To try it out, you have to install the affected packages using these tags:

Description

Allow the use of some hooks to facilitate the implementation of infinite scroll to Frontity users.

User Stories

As a Frontity theme developer
I want to use a hook to add infinite scroll to my archive pages
so that I can keep my code clean and bullet-proof

As a Frontity theme developer
I want to use a hook to add infinite scroll to my postType pages
so that I can keep my code clean and bullet-proof

Examples

Examples of how they could be used:

import { connect } from "frontity";
import useArchiveInfiniteScroll from "@frontity/hooks/use-archive-infinite-scroll";

const Archive = () => {
  const { pages, isLimit, isFetching, fetchNext } = useArchiveInfiniteScroll();

  return (
    <>
      {/* <ArchivePage /> would be a custom component created by the developer. */}
      {pages.map(({ key, link, isLast, Wrapper }) => (
        <Wrapper key={key}>
          <ArchivePage link={link} />
          {!isLast && <hr />}
        </Wrapper>
      ))}
      {/* <Loading /> would also be a custom component created by the developer. */}
      {isFetching && <Loading />}
      {isLimit && <button onClick={fetchNext}>Load Next Page</button>}
    </>
  );
};

export default connect(Archive);
import { connect } from "frontity";
import usePostTypeInfiniteScroll from "@frontity/hooks/use-post-type-infinite-scroll";

const PostTypeList = ({ state }) => {
  const data = state.source.get(state.router.link);

  const { posts, isLimit, isFetching, fetchNext } = usePostTypeInfiniteScroll({
    active: !!data.isPost,
  });

  return (
    <>
      {/* <PostType /> would be a custom component created by the developer. */}
      {posts.map(({ key, link, isLast, Wrapper }) => (
        <Wrapper key={key}>
          <PostType link={link} />
          {!isLast && <hr />}
        </Wrapper>
      ))}
      {/* <Loading /> would also be a custom component created by the developer. */}
      {isFetching && <Loading />}
      {isLimit && <button onClick={fetchNext}>Load Next Post</button>}
    </>
  );
};

export default connect(PostTypeList);

We’re planning to have two different hooks: useArchiveInfiniteScroll and usePostTypeInfiniteScroll.

These are the types of the hooks in the first beta:

type UseArchiveInfiniteScroll = (options?: {
  limit?: number;
  active?: boolean;
}) => {
  pages: {
    key: string;
    link: string;
    isLast: boolean;
    Wrapper: React.FC;
  }[];
  isLimit: boolean;
  isFetching: boolean;
  fetchNext: () => Promise<void>;
};
type UsePostTypeInfiniteScroll = (options?: {
  limit?: number;
  active?: boolean;
  archive?: string;
  fallback?: string;
}) => {
  posts: {
    key: string;
    link: string;
    isLast: boolean;
    Wrapper: React.FC;
  }[];
  isLimit: boolean;
  isFetching: boolean;
  fetchNext: () => Promise<void>;
};

You can find more information on the Pull Request where the beta version was implemented. Bear in mind that docs and e2e tests are still lacking.

In order to work with the beta, we’re going to work on the infinite-scroll-beta branch until the v1 is ready.

Hi @SantosGuillamot

I am trying to load 10 blogposts using usePostTypeInfiniteScroll. Following is my code. When I scroll down it automatically fetches the latests blogposts. Can I fetch blogposts for a specific category?

const Post = ({ state, actions, libraries }) => {
  const data = state.source.get(state.router.link);

  const { posts, isLimit, isFetching, fetchNext } = usePostTypeInfiniteScroll({
    limit: 10,
    active: !!data.isPost,
  });

  return (
    <div>
      {posts.map(({ key, link, isLast, Wrapper }) => {
        let id = state.source.get(link).id;
        let type = state.source.get(link).type;

        let post = state.source[type][id];
        let formatPost = formatPostData(state, post);
        return (
          <Wrapper key={key}>
            <div/>
            <Post post={formatPost} />
            {!isLast && <hr />}
          </Wrapper>
        );
      })}
      {isFetching && <>...loading</>}
    </div>
  );
};

export default connect(Post);
2 Likes

Found it:

  let clickedPost = getPostData(state);
  let categoryId = clickedPost.categories[0];
  let categoryLink = state.source.category[categoryId].link;

  const { posts, isLimit, isFetching, fetchNext } = usePostTypeInfiniteScroll({
    limit: 10,
    fallback: "/",
    active: !!data.isPost,
    archive: categoryLink,
  });

2 Likes

Hey, awesome :slightly_smiling_face:

@sarang please remind that this is still a beta. I’d be great if you could give us feedback on both the bugs you find and the API.

Sure!! Till now everything is working great. Thanks!!

1 Like

I have published a new beta version with a bug fix made by @orballo:

Please update your local projects.

1 Like

I ran into a new scenario where the next entity fetched by usePostTypeInfiniteScroll returns an error. I think this case should be handled by the hook, but not sure how.

Maybe it should deactivate the infinite scroll when it runs into an entity that couldn’t be fetched? Maybe just jump to the next one?

Right now, what is happening in my case, is that when I keep scrolling and the route change happens, all of a sudden my screen becomes an error screen, and it’s very confusing, even though I’m rendering an error component, any previous context just disappears.

Interesting.

What about adding a some error handling to the hook? If there’s an error, it won’t let the user continue, but it will let him/her try to do the fetching again.

That would mean that if there’s an error, the hook should “exit” the infinite-scroll mode and switch to the “isLimit” mode.

import { connect } from "frontity";
import useArchiveInfiniteScroll from "@frontity/hooks/use-archive-infinite-scroll";

const Archive = () => {
  const {
    pages,
    isLimit,
    isFetching,
    fetchNext,
    isError,
  } = useArchiveInfiniteScroll();

  return (
    <div>
      {/* <ArchivePage /> would be a custom component created by the developer. */}
      {pages.map(({ key, link, isLast, Wrapper }) => (
        <Wrapper key={key}>
          <ArchivePage link={link} />
          {!isLast && <hr />}
        </Wrapper>
      ))}
      {/* <Loading /> would also be a custom component created by the developer. */}
      {isFetching && <Loading />}
      {isError && (
        <div>
          There was an error fetching the next page. Please, try loading the
          page again.
        </div>
      )}
      {(isLimit || isError) && (
        <button onClick={fetchNext}>Load Next Page</button>
      )}
    </div>
  );
};

export default connect(Archive);

Does it make sense or is it too complex?

I don’t think the interface shown to the user is to complex, though I don’t like the logic behind. I’d not play with the isLimit logic, I’d add an isError logic that prevents the infinite scroll, and it’s set until a new fetch works.

OK, so you would make it two separate things:

  • isLimit and button the get next page.
  • isError and button to try again.

Is that right?

Yes, for the user it would be like you described. But internally the fetchNext function will be the one in charge of reseting isError.

1 Like