Spring Data REST sorting and paging with tanstack router and react query (part 4)
In the previous two articles part 2, part 3 we have built a Spring Boot application with a React and Svelte frontend that allowed us to list and edit clubs stored in a database.
Previously, we focused on validation and the frontend applications. In this article, I want to focus more on sorting and paging in the React version of the client app. For Svelte, most of the changes should be equivalent.
Paging and sorting
In order to see paging in action we simply add a few more rows into our table using the REST API.
Data Preparation
This can be done very easily with httpie.
|
|
produces
|
|
This basically sends a POST request with the following json object body
|
|
So we now repeat this POST call for a couple more times to get at least 6 or more rows into the table.
|
|
Note that for club 6 I changed the email so that we get a different order
when sorting by managerEmail
.
Paging and sorting in action
On the Spring Boot side, we are already done. Spring Data REST
automatically supports paging and sorting out of the box as our ClubRepository
inherits from PagingAndSortingRepository
(see the documentation).
If we check the response to GET http://localhost:8080/api/clubs
more closely we will find that
there is already paging information built in:
|
|
produces
|
|
So from the response it becomes clear that Spring Data REST already automatically applies paging with a page size of 20 elements, and in our result list there are currently only two elements and therefore only a single page.
We can now play with the api to test out sorting, by default Spring Data REST supports the following query parameters on the list endpoint
page
: The page number to access (0 indexed, defaults to 0).size
: The page size requested (defaults to 20).sort
: A collection of sort directives in the format($propertyname,)+[asc|desc]?
.
Lets sort by clubName
|
|
Note that this requires a unix like shell and having
httpie
and
jq
to be installed.
Here we simply request all clubs to be sorted by clubName
and then
just extract the clubName
field from the presented list of clubs.
We can also sort by managerEmail
:
|
|
Now club6
is on top since it has the alphabetically smallest managerEmail
.
We can also reduce the page size to enable paging.
|
|
Display the 2nd page:
|
|
Cool, with this information we should be able to add this to our react client.
Adding paging and sorting to the React UI
In order to get better type safety we start with defining a TypeScript
interface for our server response in src/api/api.ts
.
|
|
Then we update our fetchClubs
method to allow us to add paging
and sorting parameters.
|
|
ClubSort
only accepts attributes of the Club
object, this allows us
to get type-safe sort parameters. Also note that (1)
is the place where
we convert from our internal SortCriterium
representation to the
Spring Data REST API.
Now that we have those extra parameters we need to update our
useClubsQuery
method to be able to pass those on.
|
|
Now this is where it gets interesting. Previously, the queryKey
was
simply ['clubs']
, now we need to include all the query parameters
into the query key to enable automatic refetching as soon as the
queryKey
changes.
Note that unlike React dependencies, query keys are always
deterministically serialized, which means that dynamically
recreating the query key all the time will only lead to a re-fetching
if the query key object has actually changed (i.e. no ===
comparison but
more of a deepEquals
semantic), for more information see
the documentation.
We can now integrate the paging and sorting functionality in the UI.
In the first step we will do the naive approach where we ignore
the functionality of TanStack router and will use useState
exclusively for now.
|
|
As a first step (see (2)
) we create state variables for sorting and paging.
Then in (3)
we simply use our updated useClubsQuery
method and pass in those
state variables.
In (4)
, we extract the list of clubs out of the query response and
finally we grab a reference to the navigate
method which we can use
to programmatically navigate to other pages (when the user clicks on a row, see (5)
).
The rest is only now enabling sorting and paging on the PrimeReact DataTable.
The only complexity is calculating between the page number semantic from Spring Data REST to a first/count semantic which PrimeReact DataTable paginator expects.
When you now start the Development Server via pnpm dev
in the client-react
directory and open the browser at
Then you will see paging and sorting live.
Migrating useState
to QueryParams
Now the solution we built is quite nice but there is one small problem, if we share the URL of our app the receiver of the link won’t get the same sorting and page information.
This is one of the big benefits of the new TanStack Router. It makes
it very easy to move all the page state into the URL as query parameters.
While this was obviously also possible previously, it required you to
do a whole lot of heavy lifting to synchronize your useState
variables
with the browser URL.
TanStack Router makes this a breeze.
To update our component we update use useClubsQuery
hook
|
|
This looks very much the same as previously however, we separated the PageInfo
object since we don’t need it as a combined object.
Previously, I decided to create an own container object for Paging as
I wanted to combine pageNum
and pageSize
as you would typically
update both together.
Now, those values will be parsed straight out of the URL and therefore, we can leave them separate.
fetchClubs
pretty much stays the same apart from the updated
parameters.
|
|
Now it gets interesting. How do we parse all the query parameters out of the URL?
First we update the clubsRoute
definition as follows:
|
|
We basically define a zod
schema for the query params and now get
both type safe query params as well as automatic validation of the
parameters. This is quite ingenious and super handy (and IMHO will change
the way we will work with client side routers in the future).
Now in our page component, we can simply useSearch
and get the correctly
typed query parameters out.
|
|
We pass them straight to the useClubsQuery
hook and the rest pretty
much stays the same.
The only remaining difference is the handling of paging/sorting change
in our PrimeReact DataTable
|
|
On every sorting/paging related change we simply navigate
to the same
page with updated search
params (see (6)
).
Whenever the sorting changes we go to our current page and
update the search
object with the new sort
value which contains
both the sort-column and the sort-direction.
On the next re-render our useSearch
call will receive the updated
search params and re-fetch the useClubsQuery
automatically.
There you have it, we have a fully type safe sorting/paging experience where the whole page state is stored in the browser URL. A shared URL will contain all the relevant information including the current page and sort order.
Not only does this improve user experience since users can bookmark their favorite sorting/paging information but also it makes developing a lot nicer as well as you don’t need to re-play the same steps when reloading the page, you are straight to where you left off.
I hope you are as excited as I am about the advancements in routing that TanStack Router will bring us once its done.
If you plan to use TanStack router in your projects, be sure to stay up to date on the latest changes and expect a few breaking API changes along the way. Hopefully, we will reach a 1.0 soon which will broaden the appeal even more.
This concludes part 4 of the tutorial. In the next article we will look at filtering in more detail.
You can find the completed sources on GitHub.