skip to Main Content

I’m using React Query infinite queries to load a list of entries with a "next" button trigger (this is abbreviated code from the docs but it’s the same idea):

import { useInfiniteQuery } from '@tanstack/react-query'

function Projects() {
  const {
    data,
    error,
    fetchNextPage,
    hasNextPage,
    isFetching,
    isFetchingNextPage,
    status,
  } = useInfiniteQuery({
    queryKey: ['projects'],
    queryFn: fetchProjects,
    initialPageParam: 0,
    getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
  })

  return (
    <>
      <div>
        {/* [loop through page data] */}
      </div>
      
      <div>
        <button
          onClick={() => fetchNextPage()}
          disabled={!hasNextPage || isFetchingNextPage}
        >
          Next Page
        </button>
      </div>
    </>
  )
}

When I do this Typescript automatically assumes the types of the Infinite Query variables like data, error, etc. using the types provided by the library. Now, I want to make the Next button its own component and pass the Infinite Query variables to it as a prop, like this:

// projects.tsx

import { useInfiniteQuery } from '@tanstack/react-query';
import NextButton from './nextbutton.tsx';

function Projects() {
  const fetchProjects = async ({ pageParam }) => {
    const res = await fetch('/api/projects?cursor=' + pageParam)
    return res.json()
  }

  const {
    data,
    error,
    fetchNextPage,
    hasNextPage,
    isFetching,
    isFetchingNextPage,
    status,
  } = useInfiniteQuery({
    queryKey: ['projects'],
    queryFn: fetchProjects,
    initialPageParam: 0,
    getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
  })

  const queryData = {
    fetchNextPage: fetchNextPage,
    hasNextPage: hasNextPage,
    isFetchingNextPage: isFetchingNextPage
  }

  return (
    <>
      <div>
        {/* [loop through page data] */}
      </div>

      <div>
        <NextButton
          queryData={ queryData }
        />
      </div>
    </>
  )
}
// nextbutton.tsx

export default function NextButton({ queryData }) {
  return (
    <button
      onClick={ () => queryData.fetchNextPage() }
      disabled={ !queryData.hasNextPage || queryData.isFetchingNextPage }
    >
      Next Page
    </button>
  )
}

The trouble is that while the Infinite Query variables are typed automatically when they’re created, if I pass them as a prop to a separate component they have to be typed again.

I could just manually write out the type for each variable but it seems like there should be an easier, less repetitive way to type them. I’m fine with passing all of the variables to the button component instead of just a handful if that’s easier.

2

Answers


  1. I could just manually write out the type for each variable…

    Do this. Your components should be self-contained and clearly define the props they expect. It makes your code portable, reusable and testable.

    I would also use separate props instead of a single queryData object for clarity.

    You can directly tie your button props to the type from React Query…

    import type { UseInfiniteQueryResult } from '@tanstack/react-query';
    
    type NextButtonProps = Pick<
      UseInfiniteQueryResult,
      'fetchNextPage' | 'isFetchingNextPage' | 'hasNextPage'
    >;
    

    or provide your own definition based on the expected usage…

    interface NextButtonProps {
      fetchNextPage: () => void;
      isFetchingNextPage: boolean;
      hasNextPage: boolean;
    }
    

    Then set the component prop types…

    export default function NextButton({
      fetchNextPage,
      hasNextPage,
      isFetchingNextPage,
    }: NextButtonProps) {
      return (
        <button
          type="button"
          onClick={fetchNextPage}
          disabled={!hasNextPage || isFetchingNextPage}
        >
          Next Page
        </button>
      );
    }
    

    and use this in your parent component

    <NextButton {...queryData} />
    
    {/* or separate props */}
    
    <NextButton
      fetchNextPage={fetchNextPage}
      isFetchingNextPage={isFetchingNextPage}
      hasNextPage={hasNextPage}
    />
    
    Login or Signup to reply.
  2. You don’t need to pass your whole query to the button component, this would be overkill

    what you need to do is pass the function fetchNextPage() to the button and have it triggered with clicked on it, here is an example

    <NextButton nextPage={()=>fetchNextPage()} />
    
    
    export default function NextButton({ nextPage }) {
      return (
        <button
          onClick={ nextPage }
        >
          Next Page
        </button>
      )
    }
    
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search