Demo

Pull to refresh

Wrap list/table content in PullToRefresh from @quan-erp/shared-ui for mobile pull gestures and desktop-friendly refetch. Bind refreshing to TanStack Query refetch.

Overview

Always import PullToRefresh from @quan-erp/shared-ui — do not invent custom scroll listeners. Parent must constrain height (h-full overflow-hidden) so the gesture container can scroll.

  • refreshing — boolean loading flag for the spinner
  • onRefresh — async handler (usually query refetch)
  • success — optional completion checkmark (e.g. !isError)
  • Pair with LoadingState / ErrorState / EmptyState inside PageContent

Standard pattern

TSXpage
1import { useState } from "react"; 2import { 3 Page, 4 PageContent, 5 PullToRefresh, 6 LoadingState, 7 ErrorState, 8 EmptyState, 9} from "@quan-erp/shared-ui"; 10import { toast } from "sonner"; 11import metadata from "../../module.metadata.json" with { type: "json" }; 12import { useItemsQuery } from "./api/items.api"; 13 14export function MyPage() { 15 const [refreshing, setRefreshing] = useState(false); 16 const { data: items, refetch, isLoading, isError } = useItemsQuery(); 17 18 async function handleRefresh() { 19 setRefreshing(true); 20 const result = await refetch(); 21 if (result.isError) { 22 toast.error(result.error?.message || "Failed to refetch"); 23 } 24 setRefreshing(false); 25 } 26 27 return ( 28 <Page pluginName={metadata.name}> 29 <PageContent> 30 <div className="h-full overflow-hidden flex flex-col"> 31 <PullToRefresh 32 className="flex-1 flex flex-col relative h-full" 33 refreshing={refreshing} 34 success={!isError} 35 onRefresh={handleRefresh} 36 > 37 {isLoading ? <LoadingState /> : null} 38 {isError ? <ErrorState /> : null} 39 {!isLoading && !items?.length ? <EmptyState /> : null} 40 {/* list / table */} 41 </PullToRefresh> 42 </div> 43 </PageContent> 44 </Page> 45 ); 46}