How do I sync my URL with application state using NextJS
Asked Answered
S

4

9

I have filters for my application. I'd like to do shallow routing, and add the queries to the URL when a user changes the filters and at the same time, update my application state. However, this seems like I'm maintaining two states. Are there any best practices for this? I'd like my URL to match the application state.

Sentry answered 6/2, 2020 at 15:12 Comment(0)
F
9

Derive your app state from the url. That means that you need to change the url, and the app will re-render -> derive the new state from the url.

// somePage.jsx

import { useRouter } from 'next/router';
import { useState, useEffect } from 'react';

const somePage = () => {
  const router = useRouter();
  const [myState, setMyState] = useState({ page: 1 });
  useEffect(() => {
    setState({ page: router.query.page });
  }, [router.query.page]);

  return (
    <div>
      {JSON.stringify(myState)}
      {[1, 2, 3, 4, 5].map(page => (
        <Link href={`?page=${page}`} key={page}>
          <a>page {page}</a>
        </Link>
      ))}
    </div>
  );
};

export default somePage;

Fetterlock answered 6/2, 2020 at 18:34 Comment(1)
"Derive your app state from the url" .... Such simple advice, but very important, thankyou!Wax
S
5

You could extract this logic to a custom hook:

import { useRouter } from 'next/router';
import { useEffect, useState } from 'react';

export const useQueryState = (initialState = {}) => {
  const router = useRouter();
  const [state, setState] = useState(initialState);

  useEffect(() => {
    setState(router.query);
  }, [router.query]);

  const setQueryParams = (query = {}) => {
    router.push({ pathname: router.pathname, query }, undefined, {
      shallow: true,
    });
  };

  return [state, setQueryParams];
};


Spavin answered 28/7, 2022 at 15:31 Comment(0)
S
5

Libraries such as next-usequerystate provide this functionality out of the box. As indicated in the accepted answer, the trick is to derive the state from the router query. next-usequerystate makes this explicit by providing a hook useQueryState that can literally be used in replacement of useState.

It also has several features that come in handy to most use cases that deal with query strings (e.g. serializing/deserializing, default values, browser history options).

Minimal example from the docs:

import { useQueryState } from 'next-usequerystate'

export default () => {
  const [name, setName] = useQueryState('name')
  return (
    <>
      <h1>Hello, {name || 'anonymous visitor'}!</h1>
      <input value={name || ''} onChange={e => setName(e.target.value)} />
      <button onClick={() => setName(null)}>Clear</button>
    </>
  )
}
Sextuple answered 1/5, 2023 at 11:56 Comment(0)
C
0

Created a lib just for that - https://github.com/asmyshlyaev177/state-in-url

export const countState: CountState = { count: 0 }
type CountState = { count: number }

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

updateUrl(currState => ({...currState, count: currState.count + 1 }) )}
Colorific answered 15/7 at 18:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.