this repo has no description
0
fork

Configure Feed

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

oh viewport rendering is a thing

+122 -182
-1
.zed/settings.json
··· 99 99 ] 100 100 }, 101 101 "TypeScript": { 102 - "language_servers": ["tsgo"], 103 102 "format_on_save": "on", 104 103 "prettier": { 105 104 "allowed": false
+4
src/components/filter-form.tsx
··· 1 1 import { createContext, useContext } from "react"; 2 2 import { Input } from "~/components/ui/input"; 3 3 import { Label } from "~/components/ui/label"; 4 + import { Button } from "./ui/button"; 4 5 5 6 export const FilterSubmitContext = createContext<{ 6 7 handleSubmit: () => void; ··· 34 35 onChange={(e) => submitContext.updateNameFilter(e.target.value)} 35 36 className="mt-1" 36 37 /> 38 + <Button type="submit" className="mt-2"> 39 + Submit 40 + </Button> 37 41 </form> 38 42 </div> 39 43 );
+1 -1
src/components/pagination-nav.tsx
··· 3 3 import { Button } from "./ui/button"; 4 4 5 5 export function PaginationNav(props: { 6 - prefetch?: "intent" | false; 6 + prefetch?: "intent" | "viewport" | false; 7 7 prevOffset: number | undefined; 8 8 nextOffset: number | undefined; 9 9 to: keyof FileRoutesByPath;
+1 -3
src/routes/basic.tsx
··· 10 10 TableHeader, 11 11 TableRow, 12 12 } from "~/components/ui/table"; 13 - import { POKEMON_LIMIT, getPokemonListQueryKey, getServerPokemonListQueryFn } from "~/util/pokemon"; 14 - import { useServerFn } from "@tanstack/react-start"; 13 + import { POKEMON_LIMIT, getPokemonListQueryFn, getPokemonListQueryKey } from "~/util/pokemon"; 15 14 16 15 const searchParamsSchema = v.object({ 17 16 offset: v.optional(v.number(), 0), ··· 25 24 26 25 function RouteComponent() { 27 26 const { offset: currentOffset } = Route.useSearch(); 28 - const getPokemonListQueryFn = useServerFn(getServerPokemonListQueryFn); 29 27 30 28 const { data } = useSuspenseQuery({ 31 29 queryKey: getPokemonListQueryKey("suspense", currentOffset),
+6 -23
src/routes/debounced-preload-filters.tsx
··· 17 17 import { 18 18 POKEMON_LIMIT, 19 19 getFilteredPokemonListQueryKey, 20 - getServerFilteredPokemonListQueryFn, 20 + getFilteredPokemonListQueryFn, 21 21 } from "~/util/pokemon"; 22 22 23 23 const searchParamsSchema = v.object({ ··· 40 40 41 41 const pokemonListOptions = queryOptions({ 42 42 queryKey: newKey, 43 - queryFn: getServerFilteredPokemonListQueryFn, 43 + queryFn: getFilteredPokemonListQueryFn, 44 44 }); 45 45 46 46 return { ··· 71 71 serverPokemonListOptions.queryKey[2].offset, 72 72 newNameFilter, 73 73 ), 74 - queryFn: getServerFilteredPokemonListQueryFn, 74 + queryFn: getFilteredPokemonListQueryFn, 75 75 }); 76 76 }, 77 77 { ··· 101 101 function RouteComponent() { 102 102 const { offset: currentOffset, name: nameFilter } = Route.useSearch(); 103 103 const navigate = Route.useNavigate(); 104 - const { pokemonListOptions: serverPokemonListOptions } = Route.useRouteContext(); 105 - const queryClient = useQueryClient(); 104 + const { pokemonListOptions } = Route.useRouteContext(); 106 105 107 - const { data } = useSuspenseQuery({ 108 - ...serverPokemonListOptions, 109 - queryFn: getServerFilteredPokemonListQueryFn, 110 - }); 111 - 112 - if (data.prevOffset !== null) { 113 - void queryClient.prefetchQuery({ 114 - ...serverPokemonListOptions, 115 - queryFn: getServerFilteredPokemonListQueryFn, 116 - }); 117 - } 118 - 119 - if (data.nextOffset !== null) { 120 - void queryClient.prefetchQuery({ 121 - ...serverPokemonListOptions, 122 - queryFn: getServerFilteredPokemonListQueryFn, 123 - }); 124 - } 106 + const { data } = useSuspenseQuery(pokemonListOptions); 125 107 126 108 // Use the filtered results directly from the server 127 109 const filteredPokemon = data.pokemon; ··· 182 164 )} 183 165 184 166 <PaginationNav 167 + prefetch="viewport" 185 168 prevOffset={data.prevOffset ?? undefined} 186 169 nextOffset={data.nextOffset ?? undefined} 187 170 to="/debounced-preload-filters"
+6 -25
src/routes/filters.tsx
··· 1 1 import { createFileRoute } from "@tanstack/react-router"; 2 - import { queryOptions, useQueryClient, useSuspenseQuery } from "@tanstack/react-query"; 3 - import { useServerFn } from "@tanstack/react-start"; 2 + import { queryOptions, useSuspenseQuery } from "@tanstack/react-query"; 4 3 import { useCallback, useState } from "react"; 5 4 import * as v from "valibot"; 6 5 import { FilterForm, FilterSubmitContext } from "~/components/filter-form"; ··· 16 15 import { 17 16 POKEMON_LIMIT, 18 17 getFilteredPokemonListQueryKey, 19 - getServerFilteredPokemonListQueryFn, 18 + getFilteredPokemonListQueryFn, 20 19 } from "~/util/pokemon"; 21 20 22 21 const searchParamsSchema = v.object({ ··· 55 54 56 55 const pokemonListOptions = queryOptions({ 57 56 queryKey: newKey, 58 - queryFn: getServerFilteredPokemonListQueryFn, 57 + queryFn: getFilteredPokemonListQueryFn, 59 58 }); 60 59 61 60 return { ··· 71 70 function RouteComponent() { 72 71 const { offset: currentOffset, name: nameFilter } = Route.useSearch(); 73 72 const navigate = Route.useNavigate(); 74 - const { pokemonListOptions: serverPokemonListOptions } = Route.useRouteContext(); 75 - const queryClient = useQueryClient(); 73 + const { pokemonListOptions } = Route.useRouteContext(); 76 74 77 - const { data } = useSuspenseQuery({ 78 - ...serverPokemonListOptions, 79 - queryFn: useServerFn(getServerFilteredPokemonListQueryFn), 80 - }); 81 - 82 - if (data.prevOffset !== null) { 83 - void queryClient.prefetchQuery({ 84 - ...serverPokemonListOptions, 85 - queryKey: getFilteredPokemonListQueryKey("filters", data.prevOffset, nameFilter), 86 - }); 87 - } 88 - 89 - if (data.nextOffset !== null) { 90 - void queryClient.prefetchQuery({ 91 - ...serverPokemonListOptions, 92 - queryKey: getFilteredPokemonListQueryKey("filters", data.nextOffset, nameFilter), 93 - }); 94 - } 75 + const { data } = useSuspenseQuery(pokemonListOptions); 95 76 96 77 // Use the filtered results directly from the server 97 78 const filteredPokemon = data.pokemon; ··· 153 134 )} 154 135 155 136 <PaginationNav 156 - prefetch="intent" 137 + prefetch="viewport" 157 138 prevOffset={data.prevOffset ?? undefined} 158 139 nextOffset={data.nextOffset ?? undefined} 159 140 to="/filters"
+4 -8
src/routes/intent-preloading.tsx
··· 1 1 import { createFileRoute } from "@tanstack/react-router"; 2 2 import { queryOptions, useSuspenseQuery } from "@tanstack/react-query"; 3 - import { useServerFn } from "@tanstack/react-start"; 4 3 import * as v from "valibot"; 5 4 import { PaginationNav } from "~/components/pagination-nav"; 6 5 import { ··· 11 10 TableHeader, 12 11 TableRow, 13 12 } from "~/components/ui/table"; 14 - import { POKEMON_LIMIT, getPokemonListQueryKey, getServerPokemonListQueryFn } from "~/util/pokemon"; 13 + import { POKEMON_LIMIT, getPokemonListQueryKey, getPokemonListQueryFn } from "~/util/pokemon"; 15 14 16 15 const searchParamsSchema = v.object({ 17 16 offset: v.optional(v.number(), 0), ··· 28 27 29 28 const pokemonListOptions = queryOptions({ 30 29 queryKey: newKey, 31 - queryFn: getServerPokemonListQueryFn, 30 + queryFn: getPokemonListQueryFn, 32 31 }); 33 32 34 33 return { ··· 43 42 44 43 function RouteComponent() { 45 44 const { offset: currentOffset } = Route.useSearch(); 46 - const { pokemonListOptions: serverPokemonListOptions } = Route.useRouteContext(); 45 + const { pokemonListOptions } = Route.useRouteContext(); 47 46 48 - const { data } = useSuspenseQuery({ 49 - ...serverPokemonListOptions, 50 - queryFn: useServerFn(getServerPokemonListQueryFn), 51 - }); 47 + const { data } = useSuspenseQuery(pokemonListOptions); 52 48 53 49 return ( 54 50 <div className="p-4">
+6 -28
src/routes/pagination.tsx
··· 1 1 import { createFileRoute } from "@tanstack/react-router"; 2 - import { queryOptions, useQueryClient, useSuspenseQuery } from "@tanstack/react-query"; 3 - import { useServerFn } from "@tanstack/react-start"; 2 + import { queryOptions, useSuspenseQuery } from "@tanstack/react-query"; 4 3 import * as v from "valibot"; 5 4 import { PaginationNav } from "~/components/pagination-nav"; 6 5 import { ··· 11 10 TableHeader, 12 11 TableRow, 13 12 } from "~/components/ui/table"; 14 - import { POKEMON_LIMIT, getPokemonListQueryKey, getServerPokemonListQueryFn } from "~/util/pokemon"; 13 + import { POKEMON_LIMIT, getPokemonListQueryKey, getPokemonListQueryFn } from "~/util/pokemon"; 15 14 16 15 const searchParamsSchema = v.object({ 17 16 offset: v.optional(v.number(), 0), ··· 28 27 29 28 const pokemonListOptions = queryOptions({ 30 29 queryKey: newKey, 31 - queryFn: getServerPokemonListQueryFn, 30 + queryFn: getPokemonListQueryFn, 32 31 }); 33 32 34 33 return { ··· 43 42 44 43 function RouteComponent() { 45 44 const { offset: currentOffset } = Route.useSearch(); 46 - const { pokemonListOptions: serverPokemonListOptions } = Route.useRouteContext(); 47 - const queryClient = useQueryClient(); 48 - const getPokemonListQueryFn = useServerFn(getServerPokemonListQueryFn); 49 - 50 - const { data } = useSuspenseQuery({ 51 - ...serverPokemonListOptions, 52 - queryFn: getPokemonListQueryFn, 53 - }); 54 - 55 - if (data.prevOffset !== null) { 56 - void queryClient.prefetchQuery({ 57 - ...serverPokemonListOptions, 58 - queryFn: getPokemonListQueryFn, 59 - queryKey: getPokemonListQueryKey("pagination", data.prevOffset), 60 - }); 61 - } 45 + const { pokemonListOptions } = Route.useRouteContext(); 62 46 63 - if (data.nextOffset !== null) { 64 - void queryClient.prefetchQuery({ 65 - ...serverPokemonListOptions, 66 - queryFn: getPokemonListQueryFn, 67 - queryKey: getPokemonListQueryKey("pagination", data.nextOffset), 68 - }); 69 - } 47 + const { data } = useSuspenseQuery(pokemonListOptions); 70 48 71 49 return ( 72 50 <div className="p-4"> ··· 101 79 </TableBody> 102 80 </Table> 103 81 <PaginationNav 104 - prefetch="intent" 82 + prefetch="viewport" 105 83 prevOffset={data.prevOffset ?? undefined} 106 84 nextOffset={data.nextOffset ?? undefined} 107 85 to="/pagination"
+2 -4
src/routes/preloading.tsx
··· 1 1 import { createFileRoute } from "@tanstack/react-router"; 2 2 import { queryOptions, useSuspenseQuery } from "@tanstack/react-query"; 3 - import { useServerFn } from "@tanstack/react-start"; 4 3 import * as v from "valibot"; 5 4 import { PaginationNav } from "~/components/pagination-nav"; 6 5 import { ··· 11 10 TableHeader, 12 11 TableRow, 13 12 } from "~/components/ui/table"; 14 - import { POKEMON_LIMIT, getPokemonListQueryKey, getServerPokemonListQueryFn } from "~/util/pokemon"; 13 + import { POKEMON_LIMIT, getPokemonListQueryKey, getPokemonListQueryFn } from "~/util/pokemon"; 15 14 16 15 const searchParamsSchema = v.object({ 17 16 offset: v.optional(v.number(), 0), ··· 29 28 30 29 const pokemonListOptions = queryOptions({ 31 30 queryKey: newKey, 32 - queryFn: getServerPokemonListQueryFn, 31 + queryFn: getPokemonListQueryFn, 33 32 }); 34 33 35 34 return { ··· 48 47 49 48 const { data } = useSuspenseQuery({ 50 49 ...serverPokemonListOptions, 51 - queryFn: useServerFn(getServerPokemonListQueryFn), 52 50 }); 53 51 54 52 return (
+89
src/server/pokemon.ts
··· 1 + import { createServerFn } from "@tanstack/react-start"; 2 + import * as v from "valibot"; 3 + import { DB } from "~/data/db"; 4 + 5 + export const POKEMON_LIMIT = 10; 6 + 7 + const PokemonListParamsSchema = v.object({ 8 + offset: v.optional(v.number()), 9 + }); 10 + 11 + const FilteredPokemonListParamsSchema = v.object({ 12 + offset: v.optional(v.number()), 13 + nameFilter: v.optional(v.string()), 14 + }); 15 + 16 + const innerGetPokemonList = async (offset: number) => { 17 + // Fetch one extra item to check if there are more results 18 + const pokemon = await DB.queries.getPokemonAtOffset(offset, POKEMON_LIMIT + 1); 19 + 20 + // Check if there are more results by looking at the extra item 21 + const hasMore = pokemon.length > POKEMON_LIMIT; 22 + 23 + // Remove the extra item if it exists 24 + const results = hasMore ? pokemon.slice(0, -1) : pokemon; 25 + 26 + console.log("results", results); 27 + 28 + return { 29 + pokemon: results, 30 + nextOffset: hasMore ? offset + POKEMON_LIMIT : null, 31 + prevOffset: offset > 0 ? Math.max(0, offset - POKEMON_LIMIT) : null, 32 + }; 33 + }; 34 + 35 + const innerGetFilteredPokemonList = async (offset: number, nameFilter: string) => { 36 + // If no filter is provided, fall back to regular query 37 + if (!nameFilter.trim()) { 38 + return await innerGetPokemonList(offset); 39 + } 40 + 41 + // Fetch one extra item to check if there are more results 42 + const pokemon = await DB.queries.getFilteredPokemonAtOffset( 43 + offset, 44 + POKEMON_LIMIT + 1, 45 + `%${nameFilter.trim()}%`, 46 + ); 47 + 48 + // Check if there are more results by looking at the extra item 49 + const hasMore = pokemon.length > POKEMON_LIMIT; 50 + 51 + // Remove the extra item if it exists 52 + const results = hasMore ? pokemon.slice(0, -1) : pokemon; 53 + 54 + console.log("filtered results", results, "filter:", nameFilter); 55 + 56 + return { 57 + pokemon: results, 58 + nextOffset: hasMore ? offset + POKEMON_LIMIT : null, 59 + prevOffset: offset > 0 ? Math.max(0, offset - POKEMON_LIMIT) : null, 60 + appliedFilter: nameFilter.trim(), 61 + }; 62 + }; 63 + 64 + export const getServerPokemonList = createServerFn({ method: "GET" }) 65 + .inputValidator((params) => { 66 + const validated = v.parse(PokemonListParamsSchema, params); 67 + const offset = validated.offset ?? 0; 68 + 69 + if (offset < 0) throw new Error("Offset must be greater than or equal to 0"); 70 + 71 + return { offset }; 72 + }) 73 + .handler(async ({ data }) => { 74 + return await innerGetPokemonList(data.offset); 75 + }); 76 + 77 + export const getServerFilteredPokemonList = createServerFn({ method: "GET" }) 78 + .inputValidator((params) => { 79 + const validated = v.parse(FilteredPokemonListParamsSchema, params); 80 + const offset = validated.offset ?? 0; 81 + const nameFilter = validated.nameFilter ?? ""; 82 + 83 + if (offset < 0) throw new Error("Offset must be greater than or equal to 0"); 84 + 85 + return { offset, nameFilter }; 86 + }) 87 + .handler(async ({ data }) => { 88 + return await innerGetFilteredPokemonList(data.offset, data.nameFilter); 89 + });
+3 -89
src/util/pokemon.ts
··· 1 1 import type { QueryFunctionContext } from "@tanstack/react-query"; 2 - import { createServerFn } from "@tanstack/react-start"; 3 - import * as v from "valibot"; 4 - import { DB } from "~/data/db"; 2 + import { getServerPokemonList, getServerFilteredPokemonList } from "~/server/pokemon"; 5 3 6 4 export const POKEMON_LIMIT = 10; 7 5 8 - const PokemonListParamsSchema = v.object({ 9 - offset: v.optional(v.number()), 10 - }); 11 - 12 - const FilteredPokemonListParamsSchema = v.object({ 13 - offset: v.optional(v.number()), 14 - nameFilter: v.optional(v.string()), 15 - }); 16 - 17 - const innerGetPokemonList = async (offset: number) => { 18 - // Fetch one extra item to check if there are more results 19 - const pokemon = await DB.queries.getPokemonAtOffset(offset, POKEMON_LIMIT + 1); 20 - 21 - // Check if there are more results by looking at the extra item 22 - const hasMore = pokemon.length > POKEMON_LIMIT; 23 - 24 - // Remove the extra item if it exists 25 - const results = hasMore ? pokemon.slice(0, -1) : pokemon; 26 - 27 - console.log("results", results); 28 - 29 - return { 30 - pokemon: results, 31 - nextOffset: hasMore ? offset + POKEMON_LIMIT : null, 32 - prevOffset: offset > 0 ? Math.max(0, offset - POKEMON_LIMIT) : null, 33 - }; 34 - }; 35 - 36 - const innerGetFilteredPokemonList = async (offset: number, nameFilter: string) => { 37 - // If no filter is provided, fall back to regular query 38 - if (!nameFilter.trim()) { 39 - return await innerGetPokemonList(offset); 40 - } 41 - 42 - // Fetch one extra item to check if there are more results 43 - const pokemon = await DB.queries.getFilteredPokemonAtOffset( 44 - offset, 45 - POKEMON_LIMIT + 1, 46 - `%${nameFilter.trim()}%`, 47 - ); 48 - 49 - // Check if there are more results by looking at the extra item 50 - const hasMore = pokemon.length > POKEMON_LIMIT; 51 - 52 - // Remove the extra item if it exists 53 - const results = hasMore ? pokemon.slice(0, -1) : pokemon; 54 - 55 - console.log("filtered results", results, "filter:", nameFilter); 56 - 57 - return { 58 - pokemon: results, 59 - nextOffset: hasMore ? offset + POKEMON_LIMIT : null, 60 - prevOffset: offset > 0 ? Math.max(0, offset - POKEMON_LIMIT) : null, 61 - appliedFilter: nameFilter.trim(), 62 - }; 63 - }; 64 - 65 - const getServerPokemonList = createServerFn({ method: "GET" }) 66 - .inputValidator((params) => { 67 - const validated = v.parse(PokemonListParamsSchema, params); 68 - const offset = validated.offset ?? 0; 69 - 70 - if (offset < 0) throw new Error("Offset must be greater than or equal to 0"); 71 - 72 - return { offset }; 73 - }) 74 - .handler(async ({ data }) => { 75 - return await innerGetPokemonList(data.offset); 76 - }); 77 - 78 - const getServerFilteredPokemonList = createServerFn({ method: "GET" }) 79 - .inputValidator((params) => { 80 - const validated = v.parse(FilteredPokemonListParamsSchema, params); 81 - const offset = validated.offset ?? 0; 82 - const nameFilter = validated.nameFilter ?? ""; 83 - 84 - if (offset < 0) throw new Error("Offset must be greater than or equal to 0"); 85 - 86 - return { offset, nameFilter }; 87 - }) 88 - .handler(async ({ data }) => { 89 - return await innerGetFilteredPokemonList(data.offset, data.nameFilter); 90 - }); 91 - 92 6 export const getPokemonListQueryKey = (location: string, offset: number) => { 93 7 return ["pokemon-list", location, { offset }] as const; 94 8 }; ··· 101 15 return ["pokemon-list", location, { offset, nameFilter }] as const; 102 16 }; 103 17 104 - export const getServerPokemonListQueryFn = ({ 18 + export const getPokemonListQueryFn = async ({ 105 19 queryKey, 106 20 }: QueryFunctionContext<ReturnType<typeof getPokemonListQueryKey>>) => { 107 21 const { offset } = queryKey[2]; 108 22 return getServerPokemonList({ data: { offset } }); 109 23 }; 110 24 111 - export const getServerFilteredPokemonListQueryFn = ({ 25 + export const getFilteredPokemonListQueryFn = async ({ 112 26 queryKey, 113 27 }: QueryFunctionContext<ReturnType<typeof getFilteredPokemonListQueryKey>>) => { 114 28 const { offset, nameFilter } = queryKey[2];