Got problem on how to handle data from client to server com (pagination through query parameter)
Answered
futuredevengineer posted this in #help-forum
So, basically, I have a server component in a file called
and I have ...
blogserver.tsx
:export default async function FetchBlogs({selectedPage}:{selectedPage:number}){
const data = await fetch(`/api/blogs?page=${selectedPage}/`,{'headers':{'Accept': 'application/json'}})
const awaitData = await data.json()
const blogs = await awaitData.data
const mapBlogs = ()=>{
return(
blogs?.map((blog:PartialBlog,index:number)=>{
return(
<div key={index} className=' font-mono bg-box border-outcolor border-[1px] flex flex-row flex-wrap gap-4 justify-between rounded-[5px] hover:scale-105 transition-all duration-200 w-[700px] box-border' >
<div className=' flex flex-wrap flex-col gap-4 w-[50%] p-4'>
<div className=' text-[16px] font-semibold'>{blog.title}</div>
<div id='tags' className='flex gap-5 justify-start'>
{
blog.tags.map((tag,index)=>{
return(
<div key={index} className=' bg-black border-outcolor text-selectorcolor border-[1px] p-1 rounded-[4px] text-[12px]'>{tag}</div>
)
})
}
</div>
<div className='text-gray-400 text-[12px]'>Posted {dayjs(blog.date).fromNow()}</div>
</div>
<img src={blog.image} alt={'someimg'} className='object-cover w-[200px] h-[fill] rounded-[5px] ' />
<div id='pagination' >
</div>
</div>
)
})
)
}
return(
<div className='flex flex-wrap flex-col gap-5 justify-center items-center'>
{mapBlogs()}
<Blogs></Blogs>
</div>
)
}
and I have ...
Answered by futuredevengineer
@Alfonsus Ardani Morning Alfonsus, so I came up with a solution, however this solution is probably temporary but for time being, until i have large amount of blogs (idk what is considered large atm, but maybe more than 50).
in the backend, I would make a database call to fetch ALL the blogs, then, I would make a call in server component to the database to retrieve data, in client i would map all the data based on
so it look like this:
in the backend, I would make a database call to fetch ALL the blogs, then, I would make a call in server component to the database to retrieve data, in client i would map all the data based on
selectedPage
state and pagination component, so the setSelectedPage
track the change of page on pagination component, and so, in the array mapping of blogs, i would slice the array based on how many blogs i would skip (starting index) and how much should i limit (ending index)so it look like this:
const mapBlogs = ()=>{
const skip = selectedPage*3-3 // if you are in page 1, start index 0, if you are in page 2, start index 3 (2*3-3)
const limit = skip+3 //ending index, in page 2 start index 3, and keep going till index 6 (not included), that's 3 blogs per page
const slicedBlogs = blogs.slice(skip,limit)
return(
slicedBlogs.map((blog,index))=>{
return(
...
)
}
)
}
178 Replies
a client component that was imported into that server component up there
'use client'
import React, {useState, useEffect} from 'react'
import {Pagination} from "@nextui-org/react"
import { PartialBlog } from '../interfaces/interfaces'
import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime'
import { useRouter } from 'next/navigation'
dayjs.extend(relativeTime)
export default function Blogs(){
const [pages,setPages] = useState<number>(1)
const [selectedPage,setSelectedPage] = useState<number>(1)
const route = useRouter()
useEffect(()=>{
fetch('/blogs/countpages/',{headers:{'Accept': 'application/json'}}).then((res)=>{
res.json().then((count)=>{
setPages(count.count)
})
})
},[])
useEffect(()=>{
route.push(`$/api/blogs?page=${selectedPage}/`)
},[selectedPage])
return (
<Pagination page={selectedPage} onChange={setSelectedPage} total={pages/3}></Pagination>
)
}
and this is my main page.tsx:
I am getting
'use client'
import React, {useState, useEffect} from 'react'
import {Pagination} from "@nextui-org/react"
import { PartialBlog } from '../interfaces/interfaces'
import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime'
import { useRouter } from 'next/navigation'
dayjs.extend(relativeTime)
export default function Blogs(){
const [pages,setPages] = useState<number>(1)
const [selectedPage,setSelectedPage] = useState<number>(1)
const route = useRouter()
useEffect(()=>{
fetch('/blogs/countpages/',{headers:{'Accept': 'application/json'}}).then((res)=>{
res.json().then((count)=>{
setPages(count.count)
})
})
},[])
useEffect(()=>{
route.push(`$/api/blogs?page=${selectedPage}/`)
},[selectedPage])
return (
<Pagination page={selectedPage} onChange={setSelectedPage} total={pages/3}></Pagination>
)
}
considering i have pagination component in the client component to handle pagination, all i am trying to do, is trying to make the server component figure out what page it is on, so that it re-fetch the data based the page number and the query parameter ?page=selectedPage
I am getting
?page=undefined
atm, so it is an error.whats inside <Pagination> ?
that's nextUI component
it is client component
there is something wrong with the setSelectedPage
<Pagination page={selectedPage} onChange={(page) => {
console.log(page)
setSelectedPage(page)
}} total={pages/3}></Pagination>
what if you do this instead?
console.log(page)
setSelectedPage(page)
}} total={pages/3}></Pagination>
what if you do this instead?
oh
wait
try this
useEffect(()=>{
if(selectedPage === undefined) return
route.push(`$/api/blogs?page=${selectedPage}/`)
},[selectedPage])
lemme try
same issue actually, i am getting undefined on the server component where the fetch is happening
i mean
selectedPage
undefined on the server where it is passed as propswell
have you set a default selectedPage number?
have you tried this?
where is the page.js?
you mean the code?
yeah
this
you didnt send it
allright
page.tsx:
import { NextUIProvider } from "@nextui-org/react";
import MainPage from "./sections/main";
import { Suspense } from "react";
import FetchBlogs from "./mycomponents/blogs/serverblog";
import LoadingBlogs from "./mycomponents/blogs/loading";
export default function Home(
{searchParams}:{searchParams:{page:number}}
){
return (
<NextUIProvider>
<MainPage>
<Suspense fallback={<LoadingBlogs />}>
<FetchBlogs selectedPage={searchParams.page} />
</Suspense>
</MainPage>
</NextUIProvider>
);
}
try Home(context: any) and console.log-ing the context
"this is context"
{ params: {}, searchParams: {} }
tried this in
Home(context:any)
console.log('this is context')
console.log(context)
bruh...
removed, but same error i mean
ouch
i might need to see the bigger picture
do you have a repo?
also i think that shouldnt have a trailing slash?
i thought
fetch
by default require a trailing slash, no?thats not a fetch
thats a redirect link
also if you have search query, how does that work?
@Alfonsus Ardani https://github.com/GhaziDev/Ghazi-Portfolio
@futuredevengineer can you describe again
what happens and how did the error occur?
like, how did you get
?page=undefined
? is it through initial page load or from navigation?initial
so when you visit localhost:3000 it redirects to localhost:3000/?page=undefined?
or how
you go to the repo, there is a folder inside
this have a
now you got a folder called
and then there is
src
, it is called mycomponents
this have a
blog
folder which has a file for server component called blogserver.tsx
or something, and loading.tsx
for loading component used for suspense.now you got a folder called
sections
which has blog.tsx
which has the client componentand then there is
page.tsx
which call the server component.no? the
?page=selectedPage
is a call to the backend (actual server)im asking about what happens on the program, not the code structure
hmm
if i try
?page=1
in the server component (set a hardcoded page) it will work.so the whole problem here is, how am i going to set the state of the selected page in client component into server component?
you can do router.push() and insert the search params in there
but im asking like what actually happens vs how you want it to happen?
i mean, didn't i do it in client component i sent above, unless it was wrong?
thats why im also confused
I need to know how you want your program to behave and how it happens in reality
like what did you do? did you access localhost:3000? did you access localhost:3000/blogs? or how
what i want to happen is this: on initial load
however i am getting undefined.
selectedPage
in client component is by default set to 1
thus, with the use of userRouter
in client comp and searchParams
in page.tsx, i except a call to backend like this ?page=1
however i am getting undefined.
what URL did you access it from?
forget about the localhost, this is not call to some next frontend route, it is call to backend next
yeah, i got
api
folder.have you fixed the trailing slash issue?
same error...
trailing slash
/
is not problem cause i have been using it before in my projects.can you show me the part of code where you "call to the backend like this
?page=1
" ?import { NextRequest, NextResponse } from "next/server";
import { BlogModel } from "@/app/models/Blog";
export const GET = async (req: NextRequest, res: NextResponse) => {
const page = req.nextUrl.searchParams.get("page") as unknown as number; //supposed to be page number
const blogs = await BlogModel.find({})
.select(["title", "date", "tags", "image", "slug"])
.skip(3 * page - 3)
.limit(3) // pagination in database
// basically if you are in p.1 skip nothing, and query over the first 3 documents, in p.2 skip the first 3 and query over the 2nd 3 documents and so on
const blogsCount = await BlogModel.find({}).countDocuments()
return NextResponse.json({ data: blogs, pages:blogsCount}, { status: 200 });
};
this is in api/blogs/route.ts
btw this is working code, everything on backend is working cause if you try
?page=1
it will work, same if you try with ?page=2
in server component ( by that i mean hardcoding the query param value) so it is issue on how i want to dynamically set the query param value based on pagination
component in client component.brb
i meant the part of code where you call the backend from the client...
export default async function FetchBlogs({selectedPage}:{selectedPage:number}){
const data = await fetch(`/api/blogs?page=${selectedPage}/`,{'headers':{'Accept': 'application/json'}})
const awaitData = await data.json()
const blogs = await awaitData.data
const mapBlogs = ()=>{
return(
blogs?.map((blog:PartialBlog,index:number)=>{
return(
<div key={index} className=' font-mono bg-box border-outcolor border-[1px] flex flex-row flex-wrap gap-4 justify-between rounded-[5px] hover:scale-105 transition-all duration-200 w-[700px] box-border' >
<div className=' flex flex-wrap flex-col gap-4 w-[50%] p-4'>
<div className=' text-[16px] font-semibold'>{blog.title}</div>
<div id='tags' className='flex gap-5 justify-start'>
{
blog.tags.map((tag,index)=>{
return(
<div key={index} className=' bg-black border-outcolor text-selectorcolor border-[1px] p-1 rounded-[4px] text-[12px]'>{tag}</div>
)
})
}
</div>
<div className='text-gray-400 text-[12px]'>Posted {dayjs(blog.date).fromNow()}</div>
</div>
<img src={blog.image} alt={'someimg'} className='object-cover w-[200px] h-[fill] rounded-[5px] ' />
<div id='pagination' >
</div>
</div>
)
})
)
}
return(
<div className='flex flex-wrap flex-col gap-5 justify-center items-center'>
{mapBlogs()}
<Blogs></Blogs>
</div>
)
}
i deleted the imports here because discord msg limitthis is in
/app/src/mycomponents/blogs/serverblog.tsx
so in
this part, you call data to the backend but in backend you get
export default async function FetchBlogs({selectedPage}:{selectedPage:number}){
const data = await fetch(`/api/blogs?page=${selectedPage}/`,{'headers':{'Accept': 'application/json'}})
this part, you call data to the backend but in backend you get
undefined
?brb
yes, but try
const data = await fetch(`/api/blogs?page=1/`,{'headers':{'Accept': 'application/json'}})
for example it will workor 2 or 3
see thats what i want to know, like which point you got the error
let me check
all good
can you confirm for me that changing the page, does change the URL?
like if you press next page, it shows
?page=2
the blog is in the home page right?
i think this part
should be
useEffect(()=>{
route.push(`$/api/blogs?page=${selectedPage}/`)
},[selectedPage])
should be
useEffect(()=>{
route.push(`?page=${selectedPage}`)
},[selectedPage])
to pass data from client component to server component you need to make a call to the nextjs to request a new page and pass the data through in this case the search params
thats why i am now focusing on this useEffect
because if you did not get the searchParams at
page.js
that means there is something wrong with the redirectiononly if you get the searchParams at
page.js
you can then pass the ?page=2
into the <FetchBlogs>the whole component is not showing up in the browser cause of the
selectedPage=undefined
error lol.wait
the whole stack
thats because initially there is no searchParams
this is context
{ params: {}, searchParams: {} }
⨯ TypeError: Failed to parse URL from /api/blogs?page=[object Object]/
at async FetchBlogs (./src/app/mycomponents/blogs/serverblog.tsx:19:18)
digest: "3388973733"
Cause: TypeError [ERR_INVALID_URL]: Invalid URL
at new NodeError (node:internal/errors:405:5)
at new URL (node:internal/url:637:13)
at new Request (node:internal/deps/undici/undici:7132:25)
at fetch2 (node:internal/deps/undici/undici:10715:25)
at Object.fetch (node:internal/deps/undici/undici:11574:18)
at fetch (node:internal/process/pre_execution:242:25)
at doOriginalFetch (webpack-internal:///(rsc)/./node_modules/next/dist/server/lib/patch-fetch.js:440:24)
at eval (webpack-internal:///(rsc)/./node_modules/next/dist/server/lib/patch-fetch.js:601:24)
at async FetchBlogs (webpack-internal:///(rsc)/./src/app/mycomponents/blogs/serverblog.tsx:19:18) {
input: '/api/blogs?page=[object Object]/',
code: 'ERR_INVALID_URL'
}
GET / 200 in 1526ms
this is context
{ params: {}, searchParams: {} }
⨯ TypeError: Failed to parse URL from /api/blogs?page=[object Object]/
at async FetchBlogs (./src/app/mycomponents/blogs/serverblog.tsx:19:18)
digest: "3388973733"
Cause: TypeError [ERR_INVALID_URL]: Invalid URL
at new NodeError (node:internal/errors:405:5)
at new URL (node:internal/url:637:13)
at new Request (node:internal/deps/undici/undici:7132:25)
at fetch2 (node:internal/deps/undici/undici:10715:25)
at Object.fetch (node:internal/deps/undici/undici:11574:18)
at fetch (node:internal/process/pre_execution:242:25)
at doOriginalFetch (webpack-internal:///(rsc)/./node_modules/next/dist/server/lib/patch-fetch.js:440:24)
at eval (webpack-internal:///(rsc)/./node_modules/next/dist/server/lib/patch-fetch.js:601:24)
at async FetchBlogs (webpack-internal:///(rsc)/./src/app/mycomponents/blogs/serverblog.tsx:19:18) {
input: '/api/blogs?page=[object Object]/',
code: 'ERR_INVALID_URL'
}
same error, with searchParams as before
also wrong type
brb for 5 mins i will come back.
with this:
import { NextUIProvider } from "@nextui-org/react";
import MainPage from "./sections/main";
import { Suspense } from "react";
import FetchBlogs from "./mycomponents/blogs/serverblog";
import LoadingBlogs from "./mycomponents/blogs/loading";
export default function Home(
{searchParams}:{searchParams:{page:number}}
){
return (
<NextUIProvider>
<MainPage>
<Suspense fallback={<LoadingBlogs />}>
<FetchBlogs selectedPage={searchParams.page} />
</Suspense>
</MainPage>
</NextUIProvider>
);
}
i get the same exact errortry
{searchParams.page ?? 1} />
uh oh, it seem like even setting a hardcore value for
?page=
give the same errorwtf.
it used to work before 🤔
i dont think adding trailing slash in the end is a valid syntax.
fair i removed that
error not gone, but that a step at least
you need to be mroe specific here. i cant help if youre not being specific
i am saying, i am getting this error:
⨯ TypeError: Failed to parse URL from /api/blogs?page=[object Object]/
at async FetchBlogs (./src/app/mycomponents/blogs/serverblog.tsx:19:18)
digest: "3388973733"
Cause: TypeError [ERR_INVALID_URL]: Invalid URL
at new NodeError (node:internal/errors:405:5)
at new URL (node:internal/url:637:13)
at new Request (node:internal/deps/undici/undici:7132:25)
at fetch2 (node:internal/deps/undici/undici:10715:25)
at Object.fetch (node:internal/deps/undici/undici:11574:18)
at fetch (node:internal/process/pre_execution:242:25)
at doOriginalFetch (webpack-internal:///(rsc)/./node_modules/next/dist/server/lib/patch-fetch.js:440:24)
at eval (webpack-internal:///(rsc)/./node_modules/next/dist/server/lib/patch-fetch.js:601:24)
at async FetchBlogs (webpack-internal:///(rsc)/./src/app/mycomponents/blogs/serverblog.tsx:19:18) {
input: '/api/blogs?page=[object Object]/',
code: 'ERR_INVALID_URL'
}
GET / 200 in 1526ms
this is context
{ params: {}, searchParams: {} }
⨯ TypeError: Failed to parse URL from /api/blogs?page=[object Object]/
at async FetchBlogs (./src/app/mycomponents/blogs/serverblog.tsx:19:18)
digest: "3388973733"
Cause: TypeError [ERR_INVALID_URL]: Invalid URL
at new NodeError (node:internal/errors:405:5)
at new URL (node:internal/url:637:13)
at new Request (node:internal/deps/undici/undici:7132:25)
at fetch2 (node:internal/deps/undici/undici:10715:25)
at Object.fetch (node:internal/deps/undici/undici:11574:18)
at fetch (node:internal/process/pre_execution:242:25)
at doOriginalFetch (webpack-internal:///(rsc)/./node_modules/next/dist/server/lib/patch-fetch.js:440:24)
at eval (webpack-internal:///(rsc)/./node_modules/next/dist/server/lib/patch-fetch.js:601:24)
at async FetchBlogs (webpack-internal:///(rsc)/./src/app/mycomponents/blogs/serverblog.tsx:19:18) {
input: '/api/blogs?page=[object Object]/',
code: 'ERR_INVALID_URL'
}
i need to sleep now, will ping you tomorrow with any update if that's okay to you @Alfonsus Ardani
okayy
it literally says that
⨯ TypeError: Failed to parse URL from /api/blogs?page=[object Object]/
ure passing the wrong thing :'
no that was on the old thing where i had to pass
context:any
well what is the error of the new thingg ........
i didnt ask for old error
on
?page=1
it will say ⨯ TypeError: Failed to parse URL from /api/blogs?page=1
so same error i guess.
oh i see
in server you dont need to fetch() to your own server
wut
just call the function directly
i have used next on frontend before, not backend
first time on backend
interesting, show me some docs please
sorry for late reply
your FetchBlog is a server component
therefore its already in the server
no need to call fetch() to your own server
just import the function directly
how did i not know that
i legit thought server components only purpose is to render on server
not that i had to create an api
hmm probably whatever tutorial you were using didnt tell you that page.js and layout.js are server component by default or teaching the wrong data fetching method
Its a new mental model
so its understandable
i am using docs 😂
never in years before that you could import data fetching in a react component
oh
well you got it this far so.. well done :D
so, i will make a db call in server component
how am i supposed to pass a query parameter then?
pass query parameter from where to where?
ok so db call would have to have a filter based on
page
so, where do i pass the
page
number to, and from where?you pass the query from client component <Blogs> and then in
page.js
you catch it with the context: {searchParams: ...}, then you pass it to server component <FetchBlogs>validate first that the searchParams is in fact, a number and not a string
because search params are external input
thank you i will try tomorrow, and give a response here
okayyy
so this thread will stay open.
if the mods don't mind 🙂
they wont
@Alfonsus Ardani Morning Alfonsus, so I came up with a solution, however this solution is probably temporary but for time being, until i have large amount of blogs (idk what is considered large atm, but maybe more than 50).
in the backend, I would make a database call to fetch ALL the blogs, then, I would make a call in server component to the database to retrieve data, in client i would map all the data based on
so it look like this:
in the backend, I would make a database call to fetch ALL the blogs, then, I would make a call in server component to the database to retrieve data, in client i would map all the data based on
selectedPage
state and pagination component, so the setSelectedPage
track the change of page on pagination component, and so, in the array mapping of blogs, i would slice the array based on how many blogs i would skip (starting index) and how much should i limit (ending index)so it look like this:
const mapBlogs = ()=>{
const skip = selectedPage*3-3 // if you are in page 1, start index 0, if you are in page 2, start index 3 (2*3-3)
const limit = skip+3 //ending index, in page 2 start index 3, and keep going till index 6 (not included), that's 3 blogs per page
const slicedBlogs = blogs.slice(skip,limit)
return(
slicedBlogs.map((blog,index))=>{
return(
...
)
}
)
}
Answer
so that is a solution i guess.
btw, my brain farted a lot yesterday,
searchParams
only work if the url on Client Side has a query parameter, and i was trying to fetch from backend literally 😛niceee
i mean ideally you shouldnt fetch all the blogs to the backend
True, but like, even if I have 40 blogs, it wouldn't hurt, right?
Considering this is a portfolio website
sure