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.
This can be done very easily with httpie.
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
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:
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
Lets sort by
Note that this requires a unix like shell and having
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
club6 is on top since it has the alphabetically smallest
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
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
['clubs'], now we need to include all the query parameters
into the query key to enable automatic refetching as soon as the
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
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
exclusively for now.
As a first step (see
(2)) we create state variables for sorting and paging.
(3) we simply use our updated
useClubsQuery method and pass in those
(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
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
directory and open the browser at
Then you will see paging and sorting live.
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
with the browser URL.
TanStack Router makes this a breeze.
To update our component we update use
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
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
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
On every sorting/paging related change we simply
navigate to the same
page with updated
search params (see
Whenever the sorting changes we go to our current page and
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
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.