Reputation: 7573
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
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
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
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