antonwilhelm
antonwilhelm

Reputation: 7573

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

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?

Upvotes: 7

Views: 7039

Answers (3)

Aleksandr Smyshliaev
Aleksandr Smyshliaev

Reputation: 420

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

Can reuse same state in few components, state will be in sync.

// types are preserved
const form = { category: '', counter: 0 };

  const { urlState, setUrl } = useUrlState(form);
...
  <Input
    id="name"
    value={urlState.category}
    onChange={(ev) => setUrl({ category: ev.target.value })}
  />

NUQS is another option.

Upvotes: 0

swimmer
swimmer

Reputation: 3353

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.

Upvotes: 6

Ori Drori
Ori Drori

Reputation: 193358

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

Upvotes: 6

Related Questions