Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Next.js modify search params in server component without triggering data to refetch

I have a page with a form. I want to store the values of the form in the url so they can persist even if you accidentally refresh the page.

// Form.tsx
"use client"
export default function Form(){
    const pathname = usePathname()
    const searchParams = useSearchParams()
    const router = useRouter()
    
    const [content, setContent] = useState(searchParams.get("content") || "")
    
    useEffect(() => {
        const newSearchParams = new UrlSearchParams({ content })
        router.replace(pathname + "?" + newSearchParams.toString(), { scroll: false })
    }, [content, pathname, router])

    return <div>
        // Form ui here
    </div>

}

// page.tsx
export default async function Page(){
    const data = await getData() // fetches data from db
    
    return <div>
        // Some stuff is rendered based on data
        <Form/>
    </div>

}

The problem I'm having is that every time I add url search params, the page calls getData(), causing unnecessary fetching. Is there a way to add search params without causing data to refetch?

I know that, most of the time, you would want data to refetch when the search params change, but I don't in this scenario.

I've tried turning the page into a client component instead, which works to call getData() only once, but that doesn't seem to allow me to add metadata to the page. I've also seen people say that pages should be server components as much as possible.

Edit: I think I've got it. I just needed to wrap all of my page content inside a client component and do all my fetching there.

// Inner.tsx
"use client"
export default function Inner(){
    const [data, setData] = useState()

    useEffect(() => {
        getData().then(res => setData(res))
    }, [])

    return <div>
        //content rendered here
        <Form/>
    </div>
}

// page.tsx
export default function Page(){
    return <Inner/>
}
like image 271
egel Avatar asked Jun 29 '26 19:06

egel


2 Answers

New Solution

Okay, I figured out what I was doing wrong. I should have been using window.history.replaceState() directly instead of using NextJS's router.replace().

Lesson learned. The Next router should be used for routing logic, and not for quietly modifying the url.

    // wrong
    router.replace(pathname + "?" + newSearchParams.toString(), { scroll: false })

    // correct
    window.history.replaceState(null, "", pathname + "?" + newSearchParams.toString())

Old Solution

I decided to wrap all the contents inside an inner client component instead and do all my fetching there. This seems to work and the data only fetches once now.

// Inner.tsx
"use client"
export default function Inner(){
    const [data, setData] = useState()

    useEffect(() => {
        getData().then(res => setData(res))
    }, [])

    return <div>
        //content rendered here
        <Form/>
    </div>
}

// page.tsx
export default function Page(){
    return <Inner/>
}
like image 190
egel Avatar answered Jul 01 '26 07:07

egel


I want to store the values of the form in the url so they can persist even if you accidentally refresh the page

You can try to store in Localstorage

every time I add url search params, the page calls getData(), causing unnecessary fetching

It's because, of useEffect's dependencies which executes, router.replace().

 useEffect(() => {
        const newSearchParams = new UrlSearchParams({ content })
        router.replace(pathname + "?" + newSearchParams.toString(), { scroll: false })
    }, [content, pathname, router])

I've tried turning the page into a client component instead, which works to call getData() only once,.... I've also seen people say that pages should be server components as much as possible.

  • You may also use autocomplete="on" in form tag, so that browser can help in quick form filling.
  • Pages should be server-side because they help in metadata generation, server-side data fetching etc.

Solution :

  • Keep page server-side & import that form component (this is a client side component, at top use "use client") in page. In local storage store the form state, by making a object . Ex. form:{field1 : value1,field2 : value2}. localStorage key = form, value = Object.

  • In useEffect, check if form key exists, if exists then get it from localstorage and parse values & set them as default values in form fields, else if it doesn't exists just keep them empty.

Please Read :

  • useRouter() : https://nextjs.org/docs/app/api-reference/functions/use-router#userouter
like image 25
Beast80K Avatar answered Jul 01 '26 08:07

Beast80K



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!