Next.js Discord

Discord Forum

Got problem on how to handle data from client to server com (pagination through query parameter)

Answered
futuredevengineer posted this in #help-forum
Open in Discord
Avatar
So, basically, I have a server component in a file called 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 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(
...
)
}
)
}
View full answer

178 Replies

Avatar
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:

'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.
Avatar
whats inside <Pagination> ?
Avatar
that's nextUI component
it is client component
Avatar
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?
oh
wait
try this
    useEffect(()=>{
       if(selectedPage === undefined) return
        route.push(`$/api/blogs?page=${selectedPage}/`)
    },[selectedPage])
Avatar
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 props
Avatar
well
have you set a default selectedPage number?
Avatar
const [selectedPage,setSelectedPage] = useState<number>(1)
yes
it is 1
Avatar
have you tried this?
Avatar
.
Avatar
where is the page.js?
Avatar
you mean the code?
Avatar
yeah
Avatar
this
Avatar
you didnt send it
Avatar
oh nvm
sorry
lemme send it
Avatar
allright
Avatar
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>
  );
}
Avatar
try Home(context: any) and console.log-ing the context
Avatar
"this is context"
{ params: {}, searchParams: {} }
tried this in Home(context:any)
  console.log('this is context')

  console.log(context)
Avatar
well
Image
why is there a dollar sign there
Avatar
bruh...
Avatar
it happens
its ok
:D
Avatar
removed, but same error i mean
Avatar
ouch
i might need to see the bigger picture
do you have a repo?
also i think that shouldnt have a trailing slash?
Image
Avatar
i thought fetch by default require a trailing slash, no?
Avatar
no?
thats not a fetch
thats a redirect link
also if you have search query, how does that work?
Avatar
Avatar
@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?
Avatar
initial
Avatar
so when you visit localhost:3000 it redirects to localhost:3000/?page=undefined?
or how
Avatar
you go to the repo, there is a folder inside 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 component

and then there is page.tsx which call the server component.
no? the ?page=selectedPage is a call to the backend (actual server)
Avatar
im asking about what happens on the program, not the code structure
hmm
Avatar
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?
Avatar
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?
Avatar
i mean, didn't i do it in client component i sent above, unless it was wrong?
Avatar
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
Avatar
what i want to happen is this: on initial load 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.
Avatar
what URL did you access it from?
Avatar
forget about the localhost, this is not call to some next frontend route, it is call to backend next
Avatar
oh?
Avatar
yeah, i got api folder.
Avatar
have you fixed the trailing slash issue?
Avatar
same error...
trailing slash / is not problem cause i have been using it before in my projects.
Avatar
can you show me the part of code where you "call to the backend like this ?page=1" ?
Avatar
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
Avatar
i meant the part of code where you call the backend from the client...
Avatar
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 limit
this is in /app/src/mycomponents/blogs/serverblog.tsx
Avatar
so in

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 ?
Avatar
brb
yes, but try
 const data = await fetch(`/api/blogs?page=1/`,{'headers':{'Accept': 'application/json'}})
for example it will work
Avatar
ok
Avatar
or 2 or 3
Avatar
see thats what i want to know, like which point you got the error
let me check
Avatar
all good
Avatar
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

    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 redirection
only if you get the searchParams at page.js you can then pass the ?page=2 into the <FetchBlogs>
Avatar
the whole component is not showing up in the browser cause of the selectedPage=undefined error lol.
Avatar
ok
wait
Avatar
the whole stack
Avatar
thats because initially there is no searchParams
Avatar
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
Avatar
ya thats because this is the wrong way to pass the searchParams
Image
also wrong type
Avatar
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 error
Avatar
try {searchParams.page ?? 1} />
Avatar
uh oh, it seem like even setting a hardcore value for ?page= give the same error
wtf.
it used to work before 🤔
Avatar
Image
i dont think adding trailing slash in the end is a valid syntax.
Avatar
fair i removed that
error not gone, but that a step at least
Avatar
you need to be mroe specific here. i cant help if youre not being specific
Avatar
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
Avatar
okayy
it literally says that
⨯ TypeError: Failed to parse URL from /api/blogs?page=[object Object]/
ure passing the wrong thing :'
Avatar
no that was on the old thing where i had to pass context:any
Avatar
well what is the error of the new thingg ........
i didnt ask for old error
Avatar
on ?page=1 it will say ⨯ TypeError: Failed to parse URL from /api/blogs?page=1
so same error i guess.
Avatar
oh i see
in server you dont need to fetch() to your own server
Avatar
wut
Avatar
just call the function directly
Avatar
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
Avatar
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
Avatar
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
Avatar
i am using docs 😂
Avatar
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
Avatar
so, i will make a db call in server component
how am i supposed to pass a query parameter then?
Avatar
pass query parameter from where to where?
Avatar
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?
Avatar
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
Avatar
thank you i will try tomorrow, and give a response here
Avatar
okayyy
Avatar
so this thread will stay open.
if the mods don't mind 🙂
Avatar
they wont
Avatar
@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 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
Avatar
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 😛
Avatar
niceee
i mean ideally you shouldnt fetch all the blogs to the backend
Avatar
True, but like, even if I have 40 blogs, it wouldn't hurt, right?
Considering this is a portfolio website
Avatar
sure