this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

intent preloading

+174 -2
+9
src/components/Header.tsx
··· 27 27 Preloading Example 28 28 </Link> 29 29 </div> 30 + 31 + <div className="px-2"> 32 + <Link 33 + to="/intent-preloading" 34 + activeProps={{ className: "font-bold" }} 35 + > 36 + Intent Preloading Example 37 + </Link> 38 + </div> 30 39 </nav> 31 40 </header> 32 41 );
+165 -2
src/routes/intent-preloading.tsx
··· 1 - import { createFileRoute } from "@tanstack/react-router"; 1 + import { queryOptions, useSuspenseQuery } from "@tanstack/react-query"; 2 + import { Link, createFileRoute } from "@tanstack/react-router"; 3 + import { buttonVariants } from "~/components/ui/button"; 4 + 5 + import { useMemo } from "react"; 6 + import * as v from "valibot"; 7 + import { 8 + Table, 9 + TableBody, 10 + TableCell, 11 + TableHead, 12 + TableHeader, 13 + TableRow, 14 + } from "~/components/ui/table"; 15 + import { cn } from "~/lib/utils"; 16 + import { $pokeFetchClient } from "../data/client"; 17 + 18 + const POKEMON_LIMIT = 20; 19 + 20 + const matchPokemonIdExp = /\/api\/v2\/pokemon\/(\d+)\/?/; 21 + 22 + const searchParamsSchema = v.object({ 23 + offset: v.optional(v.number(), 0), 24 + }); 2 25 3 26 export const Route = createFileRoute("/intent-preloading")({ 27 + validateSearch: searchParamsSchema, 28 + loaderDeps: ({ search }) => ({ 29 + offset: search.offset, 30 + }), 31 + context: ({ deps }) => { 32 + const newKey = [ 33 + "pokemon-list", 34 + "intent-preloading", 35 + { limit: POKEMON_LIMIT, offset: deps.offset }, 36 + ] as const; 37 + 38 + const pokemonListOptions = queryOptions({ 39 + queryKey: newKey, 40 + queryFn: async () => { 41 + const { data, error } = await $pokeFetchClient.GET("/api/v2/pokemon/", { 42 + params: { 43 + query: { 44 + limit: POKEMON_LIMIT, 45 + offset: deps.offset, 46 + }, 47 + }, 48 + }); 49 + 50 + if (error) { 51 + throw error; 52 + } 53 + 54 + return data; 55 + }, 56 + }); 57 + 58 + return { 59 + pokemonListOptions, 60 + }; 61 + }, 62 + loader: ({ context }) => { 63 + context.queryClient.prefetchQuery(context.pokemonListOptions); 64 + }, 4 65 component: RouteComponent, 66 + notFoundComponent: NotFoundComponent, 67 + errorComponent: ErrorComponent, 68 + pendingComponent: LoadingComponent, 5 69 }); 6 70 71 + interface PokemonListResult { 72 + name: string; 73 + url: string; 74 + } 75 + 76 + function NotFoundComponent() { 77 + return <div>Not Found</div>; 78 + } 79 + 80 + function ErrorComponent() { 81 + return <div>Error</div>; 82 + } 83 + 84 + function LoadingComponent() { 85 + return <div>Loading...</div>; 86 + } 87 + 7 88 function RouteComponent() { 8 - return <div>Hello "/intent-preloading"!</div>; 89 + const { offset: currentOffset } = Route.useSearch(); 90 + const { pokemonListOptions } = Route.useRouteContext(); 91 + 92 + const { data } = useSuspenseQuery(pokemonListOptions); 93 + 94 + const previousOffset = useMemo(() => { 95 + if (data?.previous == null) { 96 + return null; 97 + } 98 + 99 + return new URL(data.previous).searchParams.get("offset") ?? null; 100 + }, [data?.previous]); 101 + 102 + const nextOffset = useMemo(() => { 103 + if (data?.next == null) { 104 + return null; 105 + } 106 + 107 + return new URL(data.next).searchParams.get("offset") ?? null; 108 + }, [data?.next]); 109 + 110 + const results = data.results ?? []; 111 + return ( 112 + <div className="p-4"> 113 + <h1 className="text-2xl font-bold mb-4"> 114 + National Pokédex: Pokémon {currentOffset + 1}- 115 + {currentOffset + POKEMON_LIMIT} 116 + </h1> 117 + <Table> 118 + <TableHeader> 119 + <TableRow> 120 + <TableHead>#</TableHead> 121 + <TableHead>Name</TableHead> 122 + <TableHead>Details</TableHead> 123 + </TableRow> 124 + </TableHeader> 125 + <TableBody> 126 + {results.map((pokemon: PokemonListResult) => ( 127 + <TableRow key={pokemon.name}> 128 + <TableCell>{pokemon.url.match(matchPokemonIdExp)?.[1]}</TableCell> 129 + <TableCell className="capitalize">{pokemon.name}</TableCell> 130 + <TableCell> 131 + <a 132 + href={pokemon.url} 133 + target="_blank" 134 + rel="noopener noreferrer" 135 + className="text-blue-600 underline" 136 + > 137 + View 138 + </a> 139 + </TableCell> 140 + </TableRow> 141 + ))} 142 + </TableBody> 143 + </Table> 144 + <div className="flex justify-center gap-4 mt-4"> 145 + <Link 146 + to="/intent-preloading" 147 + preload="intent" 148 + className={buttonVariants({ 149 + variant: "outline", 150 + className: cn(!previousOffset && "opacity-50 cursor-not-allowed"), 151 + })} 152 + search={{ offset: Number(previousOffset) }} 153 + disabled={!previousOffset} 154 + > 155 + Previous 156 + </Link> 157 + <Link 158 + to="/intent-preloading" 159 + preload="intent" 160 + search={{ offset: Number(nextOffset) }} 161 + className={buttonVariants({ 162 + variant: "outline", 163 + className: cn(!nextOffset && "opacity-50 cursor-not-allowed"), 164 + })} 165 + disabled={!nextOffset} 166 + > 167 + Next 168 + </Link> 169 + </div> 170 + </div> 171 + ); 9 172 }