How to derive state from URL, but also update the query-params whenever state changes in Next.JS?
Asked Answered
A

3

7

Beginner here, but finding this quite tricky. So some help would be appreciated!

I want to have users filter through some options. Those filters should be reflected in the URL. e.g. : http://localhost:3000/items?counter=1

Now when a user visits http://localhost:3000/items?counter=2 I want that to be reflected in state & put it in state. If the same user then changes the state somehow, I want that to be reflected in the url. I do know how to do both things.

But I feel I am running into an infinite loop here:

    useEffect(() => {
        router.push(`/items?counter=${counter}`, undefined, { shallow: true })
    }, [counter])

    useEffect(() => {
        setCounter(parseInt(router.query.counter))
    }, [router.query.counter])

How would I best derive my state from my query params but also always shallow-update the query params every time state changes?

Airport answered 28/10, 2022 at 10:21 Comment(0)
B
6

Always update only one of them, and update the other by listening to the changes of the first. Since the state is always derived from the query, I would update the state via useEffect, and always change the query directly.

This means that you don't update the state directly. Whenever you want to update the state, you need to update the query:

const updateCounterQuery = currentCounter => router.push(`/items?counter=${currentCounter}`, undefined, { shallow: true })

useEffect(() => {
  setCounter(parseInt(router.query.counter))
}, [router.query.counter])

However, why do you even need a state in this case? Always use the value that you get from the query:

const updateCounterQuery = counter => router.push(`/items?counter=${counter }`, undefined, { shallow: true })

const counter = +router.query.counter
Boisleduc answered 28/10, 2022 at 11:4 Comment(2)
Thanks for your answer. The last code snippet you posted, would I be doing this in a useeffect? Also: I wouldn't do this with shallow routing, right? Because then my component wouldn't re-render? Or am I not getting something?Airport
No. You don't need useEffect. The component re-renders because useRouter() triggers a render, and you get a new router object. Shallow routing doesn't prevent rerender, so it would work. The only thing this code does is assigning a const.Boisleduc
M
6

For more complex use cases where multiple query strings (e.g. ?counter=2&filter=true) or simultaneous updates (e.g. ?latitude=47.367&longitude=8.543) are required, it usually makes sense to abstract the logic to a hook.

Libraries like next-usequerystate solve exactly this use case. Guides and examples like this or this are good entrypoints for custom developments.

Moulding answered 1/5, 2023 at 12:6 Comment(0)
M
-1

Easier to use a library to stringify/parse params, I created one https://github.com/asmyshlyaev177/state-in-url

useEffect can create a loop if you are not careful. Like this should do the trick:

  const { state, updateState, updateUrl } = useUrlState(form);

  const timer = React.useRef(0 as unknown as NodeJS.Timeout);
  React.useEffect(() => {
    clearTimeout(timer.current);
    timer.current = setTimeout(() => {
      // using JSON.stringify internally to compare state and do router.push only if values are different
      updateUrl(state);
    }, 500);

    return () => {
      clearTimeout(timer.current);
    };
  }, [state, updateUrl]);
Moldavia answered 2/7 at 10:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.