Add infinite scroll and modify fetch api to isolate the main object from list

This commit is contained in:
jasinco 2025-06-10 23:38:19 +08:00
parent 868b5e1c36
commit a592828feb
6 changed files with 73 additions and 36 deletions

View file

@ -5,6 +5,7 @@
"name": "nimfront",
"dependencies": {
"@tailwindcss/vite": "^4.1.8",
"@tanstack/react-query": "^5.80.6",
"prettier": "^3.5.3",
"react": "^19.1.0",
"react-dom": "^19.1.0",
@ -233,6 +234,10 @@
"@tailwindcss/vite": ["@tailwindcss/vite@4.1.8", "", { "dependencies": { "@tailwindcss/node": "4.1.8", "@tailwindcss/oxide": "4.1.8", "tailwindcss": "4.1.8" }, "peerDependencies": { "vite": "^5.2.0 || ^6" } }, "sha512-CQ+I8yxNV5/6uGaJjiuymgw0kEQiNKRinYbZXPdx1fk5WgiyReG0VaUx/Xq6aVNSUNJFzxm6o8FNKS5aMaim5A=="],
"@tanstack/query-core": ["@tanstack/query-core@5.80.6", "", {}, "sha512-nl7YxT/TAU+VTf+e2zTkObGTyY8YZBMnbgeA1ee66lIVqzKlYursAII6z5t0e6rXgwUMJSV4dshBTNacNpZHbQ=="],
"@tanstack/react-query": ["@tanstack/react-query@5.80.6", "", { "dependencies": { "@tanstack/query-core": "5.80.6" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-izX+5CnkpON3NQGcEm3/d7LfFQNo9ZpFtX2QsINgCYK9LT2VCIdi8D3bMaMSNhrAJCznRoAkFic76uvLroALBw=="],
"@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="],
"@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="],

View file

@ -11,6 +11,7 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.1.8",
"@tanstack/react-query": "^5.80.6",
"prettier": "^3.5.3",
"react": "^19.1.0",
"react-dom": "^19.1.0",

View file

@ -4,6 +4,9 @@ import PostForm from './Post'
import { Zone } from './Zone'
import { Delete } from './Delete'
import { Fetch } from './Fetch'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
const queryClient = new QueryClient()
function App() {
const [zone, setZone] = useState("post")
@ -12,7 +15,7 @@ function App() {
<>
{zone == "post" && <PostForm />}
{zone == "delete" && <Delete />}
{zone == "view" && <Fetch />}
{zone == "view" && <QueryClientProvider client={queryClient}><Fetch /></QueryClientProvider>}
<Zone setZone={setZone} />
</>

View file

@ -1,21 +1,13 @@
import { useEffect, useState } from "react"
import { FetchPost, FetchPost_t, MediaPathTrc } from "./api"
import React from "react"
import { FetchPost, MediaPathTrc, SinglePost_t } from "./api"
import { z } from 'zod/v4'
import { useInfiniteQuery } from "@tanstack/react-query"
type fetched = z.infer<typeof FetchPost_t>
// type fetched = z.infer<typeof FetchPost_t>
export const Fetch = () => {
let [fetched, setFetched] = useState<fetched>([])
useEffect(() => {
FetchPost().then(e => {
if (e) {
setFetched(e)
}
}
)
}, [])
return (<div className="min-h-dvh w-dvw flex flex-col items-center gap-6 py-8 px-4">
{fetched.map(post => <div className="w-full max-w-2xl bg-gray-200 rounded-lg shadow-md p-6 flex flex-col gap-4">
const Single = ({ post }: { post: z.infer<typeof SinglePost_t> }) => {
return (
<div className="w-full max-w-2xl bg-gray-200 rounded-lg shadow-md p-6 flex flex-col gap-4">
<p className="text-gray-800 text-base leading-relaxed">{post.content}</p>
{post.enclosure.length > 0 && post.enclosure[0] != null && <div className="flex flex-row overflow-x-auto gap-3 py-2 -mx-2 px-2 border-t border-gray-200 pt-4">
{post.enclosure.map((enc, index) => {
@ -36,6 +28,43 @@ export const Fetch = () => {
<p className="self-start">PID: {post.id}</p>
<p className="self-end">: {post.post_at.toLocaleTimeString()}</p>
</div>
</div>)}
</div>)
</div>
)
}
export const Fetch = () => {
const {
data,
error,
fetchNextPage,
hasNextPage,
isFetching,
status,
} = useInfiniteQuery({
queryKey: ['id'],
queryFn: async ({ pageParam }) => {
return await FetchPost(pageParam == 0 ? undefined : pageParam)
},
initialPageParam: 0,
getNextPageParam: (lastPage, _) => {
return lastPage !== null && lastPage.length > 0 && (lastPage[lastPage.length - 1].id - 1) > 0 ? lastPage[lastPage.length - 1].id - 1 : undefined
},
})
return (
<div className="h-dvh w-dvw flex flex-col items-center gap-6 py-8 px-4 overflow-scroll" onScrollEnd={() => hasNextPage && !isFetching && fetchNextPage()}>
{status === 'pending' && <p>Loading</p>}
{status === 'error' && <p>Error: {error.message}</p>}
{data && data.pages.map((group, i) => (
<React.Fragment key={i}>
{group && group.map(post => (
<Single post={post} key={post.id} />
))}
</React.Fragment>
))}
{hasNextPage && <div className="w-full max-w-2xl bg-gray-200 rounded-lg shadow-md p-6 flex flex-col gap-4 animate-pulse h-[20dvh]" >
</div>}
{!hasNextPage && <p className="mb-15"></p>}
</div>
)
}

View file

@ -113,7 +113,7 @@ export default function PostForm() {
<div className="ml-auto flex items-center gap-3"> {/* Aligned items, added items-center */}
<label className="w-fit text-lg font-medium text-gray-700 break-keep"></label>
<input
className="border-b-2 border-gray-400 w-full text-right outline-none focus:border-gray-600 transition-colors duration-200 p-1" // Modernized signing input
className="border-b-2 border-gray-400 w-full text-right outline-none focus:border-gray-600 transition-colors duration-200 p-1 text-gray-700" // Modernized signing input
{...register('signing', {
maxLength: 20,
})}

View file

@ -6,9 +6,7 @@ type Inputs = {
signing?: string
files?: FileList
}
export const FetchPost_t = z.array(
z.object({
export const SinglePost_t = z.object({
id: z.number(),
content: z.string(),
signing: z.nullable(z.string()),
@ -16,8 +14,9 @@ export const FetchPost_t = z.array(
heart: z.number(),
igid: z.nullable(z.string()),
enclosure: z.array(z.nullable(z.string())),
})
)
})
export const FetchPost_t = z.array(SinglePost_t)
export const Post = async (post_form: Inputs) => {
// Delete Blank Item