Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pagination broken for previous pages #443

Open
5 tasks done
keenleads opened this issue Dec 7, 2023 · 2 comments
Open
5 tasks done

Pagination broken for previous pages #443

keenleads opened this issue Dec 7, 2023 · 2 comments
Assignees

Comments

@keenleads
Copy link

Describe the bug

1st Issue:
Pagination component and hooks are working for the next page but not really working for previous page. The button is not enabled to go backwards. You can check that by adding enough CRUD items.

I fixed it by modifying the first useState() in the usePaginatedQuery.hook.ts hook. The first UseState() is setting cachedCursors to an empty[] when it finds the last cursor in the array. It’s not necessary. I changed it to only reset if it finds start cursor which it will never have.

Would like a better fix for the above. I believe the first useState() is not necessary.

2nd Issue:
Also when you are on page > 1 and then you click on Dashboard and when you return back to CRUD, it shows the page results you were on before with the pagination. HasNext works correctly so it shows the bottom for next page but you can’t go back to the previous page because cachedCursors array is empty and doesn’t have any of the previous page last cursors. I think Apollo Client is caching the last results and returning it on the page where we left off but it breaks the hasPrevious variable and there is no way to backwards. Especially if you are on the last page, both next and previous buttons are disabled and only way to get to page one, is to refresh the browser.

Need to make modifications to the usePaginatedQuery hook and fix to get the cachedCursors to work correctly with this cursor based pagination model.

Steps to reproduce

  1. Go to CRUD page
  2. Add multiple records to create 3+ pages of records
  3. Go to next page and you will see that you can't return back to previous page
  4. Make the above fix in the first useState() in usePaginatedQuery to avoid having the cachedCursors array reset. You will then be able to go to the previous pages.
  5. With the above pages, now navigate to page 2+ in CRUD pages. Click on Dashboard to unmount CRUD react component. Click back on CRUD, you will see the last page you were on. However, previous button will be disabled. So if you left off on page 3, you have no way to go back to page 2 or 1, unless you refresh the browser and start from page 1 again.

System Info

this is a functionality issue.

Logs

No response

Validations

@keenleads
Copy link
Author

The same issue happens when you go to CRUD edit page and come back, or CRUD add page and come back and also when you go to CRUD details page and click back to get to the list page. The previous button is disabled.

@gabrii
Copy link

gabrii commented Dec 13, 2023

Hi, I don't have time to open a PR at the moment, but this is my fixed version in case someone needs it or as a reference for the PR:

import { QueryHookOptions, TypedDocumentNode, useQuery } from '@apollo/client';
import { useCallback, useEffect, useState } from 'react';
import { Exact, InputMaybe } from '@sb/webapp-api-client/graphql';

type CursorsInput = Exact<{
  first?: InputMaybe<number> | undefined;
  after?: InputMaybe<string> | undefined;
  last?: InputMaybe<number> | undefined;
  before?: InputMaybe<string> | undefined;
  exclude?: InputMaybe<string[]> | undefined;
}>;

type ExtractGeneric<Type> = Type extends TypedDocumentNode<infer QueryData> ? QueryData : never;

/**
 * An usePaginatedQuery is a hook that allows you to retrieve data with ready-made logic for cursor-based bidirectional pagination.
 * Underneath, it uses [`useQuery`](https://www.apollographql.com/docs/react/development-testing/static-typing/#usequery)
 * function exported by `@apollo/client`.
 *
 * @example
 * ```tsx showLineNumbers
 * import { usePaginatedQuery } from '@sb/webapp-api-client/hooks';
 * import { Pagination } from '@sb/webapp-core/components/pagination';
 *
 * const ITEMS_PER_PAGE = 8;
 *
 * const CrudDemoList = () => {
 *   const { data, loading, hasNext, hasPrevious, loadNext, loadPrevious } =
 *     usePaginatedQuery(crudDemoItemListQuery, {
 *       hookOptions: {
 *         variables: {
 *           first: ITEMS_PER_PAGE,
 *         },
 *       },
 *       dataKey: 'allCrudDemoItems',
 *     });
 *
 *   return (
 *     <Pagination
 *       hasNext={hasNext}
 *       hasPrevious={hasPrevious}
 *       loadNext={loadNext}
 *       loadPrevious={loadPrevious}
 *     />
 *   );
 * };
 * ```
 *
 */

export const usePaginatedQuery = <T extends TypedDocumentNode>(
  query: T,
  options: {
    hookOptions?: QueryHookOptions<ExtractGeneric<T>, CursorsInput>;
    dataKey: keyof ExtractGeneric<T>;
  }
) => {
  const [currentCursor, setCurrentCursor] = useState<string | undefined>(undefined);

  const [cachedCursors, setCachedCursors] = useState<Array<string>>([]);
  const [hasPrevious, setHasPrevious] = useState<boolean>(false);
  const [hasNext, setHasNext] = useState<boolean>(false);
  const { data, loading, fetchMore } = useQuery<ExtractGeneric<T>, CursorsInput>(query, {
    ...options.hookOptions,
    variables: {
      ...options.hookOptions?.variables,
      after: currentCursor || options.hookOptions?.variables?.after,
    },
  });



  useEffect(() => {
    setHasPrevious(cachedCursors.length > 0);
    setHasNext(data?.[options.dataKey]?.pageInfo.hasNextPage ?? false);
  }, [data, cachedCursors.length, options.dataKey]);

  const loadNext = useCallback(() => {
    const queryData = data?.[options.dataKey];
    const endCursor = queryData?.pageInfo.endCursor;
    setCurrentCursor(endCursor);
    setCachedCursors((prev) => [...prev, endCursor]);
    fetchMore({
      variables: {
        after: endCursor,
      },
      updateQuery: (_, { fetchMoreResult }) => {
        return fetchMoreResult;
      },
    });
  }, [data, setCachedCursors, fetchMore, options.dataKey]);

  const loadPrevious = useCallback(() => {
    const newCachedCursors = cachedCursors.slice(0, -1);
    setCachedCursors(newCachedCursors);

    const lastEndCursor = newCachedCursors.length > 0 ? newCachedCursors[newCachedCursors.length - 1] : undefined;
    setCurrentCursor(lastEndCursor);

    fetchMore({
      variables: {
        after: lastEndCursor,
      },
      updateQuery: (_, { fetchMoreResult }) => {
        return fetchMoreResult;
      },
    });
  }, [cachedCursors, setCachedCursors, fetchMore]);

  return { data, loading, hasNext, hasPrevious, loadNext, loadPrevious };
};

You can ignore the "exclude" parameter which is for my extended logic to exclude a series of results (I'm reusing the pagination for a selection component)

@sdrejkarz sdrejkarz self-assigned this May 23, 2024
@sdrejkarz sdrejkarz mentioned this issue May 24, 2024
3 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants