#Migrate pagination from React to Solid Start

20 messages · Page 1 of 1 (latest)

strong scaffold
#

Hook

function usePagination(rawProps: UsePaginationProps) {
  const props = mergeProps(
    {
      pageSize: DEFAULT_PAGINATION_OFFSET,
      siblingCount: 1,
      currentPage: 1,
      name: 'resultados',
    },
    rawProps,
  );

  const start = createMemo(() =>
    Math.ceil(props.pageSize * (props.currentPage - 1) + 1),
  );
  const end = createMemo(() => Math.ceil(props.pageSize * props.currentPage));
  const info = createMemo(() =>
    getPaginationInfo(end(), props.numberOfResults, props.name),
  );
  const numberOfPages = createMemo(() =>
    Math.ceil(props.numberOfResults / props.pageSize),
  );

  const pageNumbers = createMemo(() =>
    getPageNumbers(numberOfPages(), props.currentPage, props.siblingCount),
  );

  return createMemo(() => ({
    start: start(),
    end: end(),
    info: info(),
    numberOfPages: numberOfPages(),
    pageNumbers: pageNumbers(),
  }));
}
  • getPaginationInfo only returns the string like 1 of 10 posts
#

getPageNumbers

function getPageNumbers(numberOfPages: number, currentPage = 1, siblingCount = 1) {
  const totalPageNumbers = siblingCount + 5;
  const itemsCount = 5;
  const firstPageIndex = 1;
  const lastPageIndex = numberOfPages;

  if (totalPageNumbers >= numberOfPages) {
    return range(1, numberOfPages);
  }

  const leftSiblingIndex = Math.max(currentPage - siblingCount, 1);
  const rightSiblingIndex = Math.min(currentPage + siblingCount, numberOfPages);

  const shouldShowLeftDots = leftSiblingIndex > itemsCount - 2;
  const shouldShowRightDots = rightSiblingIndex < numberOfPages - 1;
  if (!shouldShowLeftDots && shouldShowRightDots) {
    const leftRange = range(1, itemsCount);
    return [...leftRange, DOTS, numberOfPages];
  }

  if (shouldShowLeftDots && !shouldShowRightDots) {
    const rightRange = range(numberOfPages - itemsCount + 1, numberOfPages);
    return [firstPageIndex, DOTS, ...rightRange];
  }

  const middleRange = range(leftSiblingIndex, rightSiblingIndex);
  return [firstPageIndex, DOTS, ...middleRange, DOTS, lastPageIndex];
}
#

Pagination.tsx

const Pagination = (rawProps: PaginationProps) => {
  const props = mergeProps(
    {
      currentPage: 1,
      numberOfResults: 0,
      pageSize: DEFAULT_PAGINATION_OFFSET,
      name: 'resultados',
      classList: {
        info: '',
        navigation: '',
        item: '',
        container: '',
      },
    },
    rawProps,
  );
  const [paginationProps, infoProps, commonProps] = splitProps(
    props,
    ['currentPage', 'name', 'numberOfResults', 'onPageChange', 'pageSize'],
    ['infoTextProps'],
  );
  const pagination = usePagination({
    currentPage: paginationProps.currentPage,
    name: paginationProps.name,
    numberOfResults: paginationProps.numberOfResults,
    pageSize: paginationProps.pageSize,
    siblingCount: 1,
  });

  return (
    <footer class={cn('flex w-full items-center justify-between gap-4')}>
      <p class={cn('text-greyBlue-700 text-small')}>{pagination().info}</p>

      <nav aria-label="pagination" class="flex items-center gap-6">
        <ul class="flex items-center justify-end gap-2">
          <PaginationNavigation
            currentPage={paginationProps.currentPage}
            onPageChange={paginationProps.onPageChange}
            type="previous"
          />

          <For each={pagination().pageNumbers}>
            {(pageNumber, index) => (
              <PaginationItem
                currentPage={paginationProps.currentPage}
                onPageChange={paginationProps.onPageChange}
                pageNumber={pageNumber}
              />
            )}
          </For>

          <PaginationNavigation
            currentPage={paginationProps.currentPage}
            lastPage={pagination().numberOfPages}
            onPageChange={paginationProps.onPageChange}
            type="next"
          />
        </ul>
      </nav>
    </footer>
  );
};
#

The error that appears on browser console always point to the return line

#

Usage

function BlogRouter() {
  const [currentPage, setCurrentPage] = createSignal(1);
  const [searchParams, setSearchParams] = useSearchParams();
  const data = createAsync(() => getPaginatedPosts(currentPage()));

  createEffect(() => setSearchParams({ page: currentPage() }));

  return (
    <>
      <PageSeo title={'Blog'} image={'/media/design.webp'} />
      <HeroSection
        title={'Blog'}
        image={{
          alt: 'Blog',
          src: '/media/design.webp',
        }}
      />

      <Suspense>
        <Show when={data()} keyed>
          {(result) => (
            <section class="flex flex-col gap-4 py-16">
              <div class="container grid grid-cols-1 gap-4 md:grid-cols-2 md:gap-6 lg:grid-cols-3 xl:grid-cols-4">
                <For each={result.data}>
                  {(post, index) => (
                    <BlogPostCard {...post} data-aos-delay={index() * 200} />
                  )}
                </For>
              </div>

              <div class="container">
                <Pagination
                  currentPage={currentPage()}
                  name={'posts'}
                  numberOfResults={result.total}
                  onPageChange={(page) => setCurrentPage(page)}
                  pageSize={DEFAULT_PAGINATION_OFFSET}
                />
              </div>
            </section>
          )}
        </Show>
      </Suspense>
    </>
  );
}
granite musk
#

I'm guessing numberOfResults and stuff are different on the client and server since the props are only read once rather than being reactive

strong scaffold
#

Any suggestion?

granite musk
#

Make usePagination take an accessor that returns the object so that it can be reactive

strong scaffold
#

the props being an accessor instead of an object?

granite musk
#

yeah

strong scaffold
#

Ok, so:

const pagination = usePagination(() => ({
    currentPage: paginationProps.currentPage,
    name: paginationProps.name,
    numberOfResults: paginationProps.numberOfResults,
    pageSize: paginationProps.pageSize,
    siblingCount: 1,
  }));
#
function usePagination(rawProps: Accessor<UsePaginationProps>) {
  const props = mergeProps(
    {
      pageSize: DEFAULT_PAGINATION_OFFSET,
      siblingCount: 1,
      currentPage: 1,
      name: 'resultados',
    },
    rawProps(),
  );
#

And now I have two errors in different places

#

Line 55 on the usage

#

And line 71

granite musk
#

make props a function too

#

calling rawProps in the body of usePagination still causes numberOfResults etc to not be reactive

strong scaffold
#

so props = () => mergeProps()

#

Still not working :/

#

So the usePagination at this point are like this:

function usePagination(rawProps: Accessor<UsePaginationProps>) {
  const props = () =>
    mergeProps(
      {
        pageSize: DEFAULT_PAGINATION_OFFSET,
        siblingCount: 1,
        currentPage: 1,
        name: 'resultados',
      },
      rawProps(),
    );

  const start = createMemo(() =>
    Math.ceil(props().pageSize * (props().currentPage - 1) + 1),
  );
  const end = createMemo(() => Math.ceil(props().pageSize * props().currentPage));
  const info = createMemo(() =>
    getPaginationInfo(end(), props().numberOfResults, props().name),
  );
  const numberOfPages = createMemo(() =>
    Math.ceil(props().numberOfResults / props().pageSize),
  );

  const pageNumbers = createMemo(() =>
    getPageNumbers(numberOfPages(), props().currentPage, props().siblingCount),
  );

  return createMemo(() => ({
    start: start(),
    end: end(),
    info: info(),
    numberOfPages: numberOfPages(),
    pageNumbers: pageNumbers(),
  }));
}