this repo has no description
0
fork

Configure Feed

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

clone filters

+238
+9
src/components/Header.tsx
··· 49 49 Filters Example 50 50 </Link> 51 51 </div> 52 + 53 + <div className="px-2"> 54 + <Link 55 + to="/debounced-preload-filters" 56 + activeProps={{ className: "font-bold" }} 57 + > 58 + Debounced Preload Filters Example 59 + </Link> 60 + </div> 52 61 </nav> 53 62 </header> 54 63 );
+35
src/routeTree.gen.ts
··· 17 17 import { Route as PaginationRouteImport } from './routes/pagination' 18 18 import { Route as IntentPreloadingRouteImport } from './routes/intent-preloading' 19 19 import { Route as FiltersRouteImport } from './routes/filters' 20 + import { Route as DebouncedPreloadFiltersRouteImport } from './routes/debounced-preload-filters' 20 21 import { Route as BasicRouteImport } from './routes/basic' 21 22 import { Route as IndexRouteImport } from './routes/index' 22 23 ··· 46 47 getParentRoute: () => rootRoute, 47 48 } as any) 48 49 50 + const DebouncedPreloadFiltersRoute = DebouncedPreloadFiltersRouteImport.update({ 51 + id: '/debounced-preload-filters', 52 + path: '/debounced-preload-filters', 53 + getParentRoute: () => rootRoute, 54 + } as any) 55 + 49 56 const BasicRoute = BasicRouteImport.update({ 50 57 id: '/basic', 51 58 path: '/basic', ··· 76 83 preLoaderRoute: typeof BasicRouteImport 77 84 parentRoute: typeof rootRoute 78 85 } 86 + '/debounced-preload-filters': { 87 + id: '/debounced-preload-filters' 88 + path: '/debounced-preload-filters' 89 + fullPath: '/debounced-preload-filters' 90 + preLoaderRoute: typeof DebouncedPreloadFiltersRouteImport 91 + parentRoute: typeof rootRoute 92 + } 79 93 '/filters': { 80 94 id: '/filters' 81 95 path: '/filters' ··· 127 141 FileRoutesByPath['/basic']['fullPath'] 128 142 > 129 143 } 144 + declare module './routes/debounced-preload-filters' { 145 + const createFileRoute: CreateFileRoute< 146 + '/debounced-preload-filters', 147 + FileRoutesByPath['/debounced-preload-filters']['parentRoute'], 148 + FileRoutesByPath['/debounced-preload-filters']['id'], 149 + FileRoutesByPath['/debounced-preload-filters']['path'], 150 + FileRoutesByPath['/debounced-preload-filters']['fullPath'] 151 + > 152 + } 130 153 declare module './routes/filters' { 131 154 const createFileRoute: CreateFileRoute< 132 155 '/filters', ··· 169 192 export interface FileRoutesByFullPath { 170 193 '/': typeof IndexRoute 171 194 '/basic': typeof BasicRoute 195 + '/debounced-preload-filters': typeof DebouncedPreloadFiltersRoute 172 196 '/filters': typeof FiltersRoute 173 197 '/intent-preloading': typeof IntentPreloadingRoute 174 198 '/pagination': typeof PaginationRoute ··· 178 202 export interface FileRoutesByTo { 179 203 '/': typeof IndexRoute 180 204 '/basic': typeof BasicRoute 205 + '/debounced-preload-filters': typeof DebouncedPreloadFiltersRoute 181 206 '/filters': typeof FiltersRoute 182 207 '/intent-preloading': typeof IntentPreloadingRoute 183 208 '/pagination': typeof PaginationRoute ··· 188 213 __root__: typeof rootRoute 189 214 '/': typeof IndexRoute 190 215 '/basic': typeof BasicRoute 216 + '/debounced-preload-filters': typeof DebouncedPreloadFiltersRoute 191 217 '/filters': typeof FiltersRoute 192 218 '/intent-preloading': typeof IntentPreloadingRoute 193 219 '/pagination': typeof PaginationRoute ··· 199 225 fullPaths: 200 226 | '/' 201 227 | '/basic' 228 + | '/debounced-preload-filters' 202 229 | '/filters' 203 230 | '/intent-preloading' 204 231 | '/pagination' ··· 207 234 to: 208 235 | '/' 209 236 | '/basic' 237 + | '/debounced-preload-filters' 210 238 | '/filters' 211 239 | '/intent-preloading' 212 240 | '/pagination' ··· 215 243 | '__root__' 216 244 | '/' 217 245 | '/basic' 246 + | '/debounced-preload-filters' 218 247 | '/filters' 219 248 | '/intent-preloading' 220 249 | '/pagination' ··· 225 254 export interface RootRouteChildren { 226 255 IndexRoute: typeof IndexRoute 227 256 BasicRoute: typeof BasicRoute 257 + DebouncedPreloadFiltersRoute: typeof DebouncedPreloadFiltersRoute 228 258 FiltersRoute: typeof FiltersRoute 229 259 IntentPreloadingRoute: typeof IntentPreloadingRoute 230 260 PaginationRoute: typeof PaginationRoute ··· 234 264 const rootRouteChildren: RootRouteChildren = { 235 265 IndexRoute: IndexRoute, 236 266 BasicRoute: BasicRoute, 267 + DebouncedPreloadFiltersRoute: DebouncedPreloadFiltersRoute, 237 268 FiltersRoute: FiltersRoute, 238 269 IntentPreloadingRoute: IntentPreloadingRoute, 239 270 PaginationRoute: PaginationRoute, ··· 252 283 "children": [ 253 284 "/", 254 285 "/basic", 286 + "/debounced-preload-filters", 255 287 "/filters", 256 288 "/intent-preloading", 257 289 "/pagination", ··· 263 295 }, 264 296 "/basic": { 265 297 "filePath": "basic.tsx" 298 + }, 299 + "/debounced-preload-filters": { 300 + "filePath": "debounced-preload-filters.tsx" 266 301 }, 267 302 "/filters": { 268 303 "filePath": "filters.tsx"
+194
src/routes/debounced-preload-filters.tsx
··· 1 + import { 2 + queryOptions, 3 + useQueryClient, 4 + useSuspenseQuery, 5 + } from "@tanstack/react-query"; 6 + import { useServerFn } from "@tanstack/react-start"; 7 + import { useCallback, useState } from "react"; 8 + import * as v from "valibot"; 9 + import { PaginationNav } from "~/components/pagination-nav"; 10 + import { Input } from "~/components/ui/input"; 11 + import { Label } from "~/components/ui/label"; 12 + import { 13 + Table, 14 + TableBody, 15 + TableCell, 16 + TableHead, 17 + TableHeader, 18 + TableRow, 19 + } from "~/components/ui/table"; 20 + import { 21 + POKEMON_LIMIT, 22 + getFilteredPokemonListQueryKey, 23 + getServerFilteredPokemonListQueryFn, 24 + } from "~/util/pokemon"; 25 + 26 + const searchParamsSchema = v.object({ 27 + offset: v.optional(v.number(), 0), 28 + name: v.optional(v.string(), ""), 29 + }); 30 + 31 + export const Route = createFileRoute({ 32 + validateSearch: searchParamsSchema, 33 + loaderDeps: ({ search }) => ({ 34 + offset: search.offset, 35 + name: search.name, 36 + }), 37 + context: ({ deps }) => { 38 + const newKey = getFilteredPokemonListQueryKey( 39 + "filters", 40 + deps.offset, 41 + deps.name, 42 + ); 43 + 44 + const pokemonListOptions = queryOptions({ 45 + queryKey: newKey, 46 + queryFn: getServerFilteredPokemonListQueryFn, 47 + }); 48 + 49 + return { 50 + pokemonListOptions, 51 + }; 52 + }, 53 + loader: ({ context }) => { 54 + context.queryClient.prefetchQuery(context.pokemonListOptions); 55 + }, 56 + component: RouteComponent, 57 + }); 58 + 59 + function RouteComponent() { 60 + const { offset: currentOffset, name: nameFilter } = Route.useSearch(); 61 + const navigate = Route.useNavigate(); 62 + const { pokemonListOptions: serverPokemonListOptions } = 63 + Route.useRouteContext(); 64 + const queryClient = useQueryClient(); 65 + 66 + const { data } = useSuspenseQuery({ 67 + ...serverPokemonListOptions, 68 + queryFn: useServerFn(getServerFilteredPokemonListQueryFn), 69 + }); 70 + 71 + if (data.prevOffset !== null) { 72 + void queryClient.prefetchQuery({ 73 + ...serverPokemonListOptions, 74 + queryKey: getFilteredPokemonListQueryKey( 75 + "filters", 76 + data.prevOffset, 77 + nameFilter, 78 + ), 79 + }); 80 + } 81 + 82 + if (data.nextOffset !== null) { 83 + void queryClient.prefetchQuery({ 84 + ...serverPokemonListOptions, 85 + queryKey: getFilteredPokemonListQueryKey( 86 + "filters", 87 + data.nextOffset, 88 + nameFilter, 89 + ), 90 + }); 91 + } 92 + 93 + // Use the filtered results directly from the server 94 + const filteredPokemon = data.pokemon; 95 + 96 + return ( 97 + <div className="p-4"> 98 + <h1 className="text-2xl font-bold mb-4"> 99 + National Pokédex: Pokémon {currentOffset + 1}- 100 + {currentOffset + POKEMON_LIMIT} (Filtered) 101 + </h1> 102 + 103 + {/* Filter UI */} 104 + <div className="mb-6 p-4 border rounded-lg bg-gray-50"> 105 + <h2 className="text-lg font-semibold mb-3">Filters</h2> 106 + <FilterForm 107 + key={`filter-form-${nameFilter}`} 108 + handleSubmit={(nameFilter) => { 109 + navigate({ 110 + search: { name: nameFilter }, 111 + }); 112 + }} 113 + initialName={nameFilter} 114 + /> 115 + </div> 116 + 117 + <Table> 118 + <TableHeader> 119 + <TableRow> 120 + <TableHead>#</TableHead> 121 + <TableHead>Name</TableHead> 122 + <TableHead>Details</TableHead> 123 + </TableRow> 124 + </TableHeader> 125 + <TableBody> 126 + {filteredPokemon.map((pokemon) => ( 127 + <TableRow key={pokemon.name}> 128 + <TableCell>{pokemon.id}</TableCell> 129 + <TableCell className="capitalize">{pokemon.name}</TableCell> 130 + <TableCell> 131 + {pokemon.types.map((type) => ( 132 + <span 133 + key={type.type.name} 134 + className="inline-block px-2 py-1 mr-1 text-sm font-medium rounded-full bg-gray-100" 135 + > 136 + {type.type.name} 137 + </span> 138 + ))} 139 + </TableCell> 140 + </TableRow> 141 + ))} 142 + </TableBody> 143 + </Table> 144 + 145 + {filteredPokemon.length === 0 && nameFilter && ( 146 + <div className="text-center py-8 text-gray-500"> 147 + No Pokemon found matching "{nameFilter}" 148 + </div> 149 + )} 150 + 151 + <PaginationNav 152 + prevOffset={data.prevOffset ?? undefined} 153 + nextOffset={data.nextOffset ?? undefined} 154 + to="/filters" 155 + /> 156 + </div> 157 + ); 158 + } 159 + 160 + function FilterForm(props: { 161 + handleSubmit: (nameFilter: string) => void; 162 + initialName: string; 163 + }) { 164 + const [nameFilter, setNameFilter] = useState(props.initialName); 165 + 166 + const onSubmit = useCallback( 167 + (e: React.FormEvent<HTMLFormElement>) => { 168 + e.preventDefault(); 169 + props.handleSubmit(nameFilter); 170 + }, 171 + [nameFilter, props], 172 + ); 173 + 174 + return ( 175 + <div className="space-y-4"> 176 + <form onSubmit={onSubmit}> 177 + <Label htmlFor="name-filter" className="text-sm font-medium"> 178 + Filter by Name 179 + </Label> 180 + <Input 181 + id="name-filter" 182 + type="text" 183 + placeholder="Enter Pokemon name..." 184 + value={nameFilter} 185 + onChange={(e) => setNameFilter(e.target.value)} 186 + className="mt-1" 187 + /> 188 + <p className="text-xs text-gray-500 mt-1"> 189 + Current filter: "{nameFilter}" (dummy UI - not functional yet) 190 + </p> 191 + </form> 192 + </div> 193 + ); 194 + }