Now we can already start the dev server and see our generated application:
We open the browser at http://127.0.0.1:5173/ and are greeted with
the scaffolded sample app:
Integrating the router
First we integrate tanstack router by adding a root route in client-react/src/pages/root.tsx:
Let’s add a few pages:
The Homepage src/pages/index.tsx
the club list src/pages/club/index.tsx
the individual Club page and src/pages/club/club.tsx with the content
src/pages/club/edit.tsx with content
Now we can hook those up in src/main.tsx
Once we have all that we are greeted with a beautiful navigation header
which allows us to switch between the pages:
Fixing the styling
While this is looking not too bad from a functionality perspective, it
lacks visual style. So let’s integrate primereact a little bit better
and fix some of the styling issues:
rename index.css to index.scss and replace its content with
create src/pages/root.module.scss with the following content
and import it into src/pages/root.tsx
Now it should look as follows:
While this is not particularily pretty, its good enough for this
tutorial and will also ensure that all the
PrimeReact components
have proper styling.
Integrating react-query
For data loading we will use react-query which needs to be set up in src/main.tsx.
we simply need to create a QueryClient and wrap the whole application
in a QueryClientProvider like so:
Before we can load the list of clubs we first need to setup the loaders
and the data model.
Since we are using zod for validation
we can already define the REST models like that in src/model/model.ts
Then we define the api methods in src/api/api.ts
This allows us to load the list of clubs, fetch a single club as well as update an existing club.
One thing missing is the proxy configuration in the frontend that
will allow us to connect to the spring backend. Add the following lines to client-react/vite.config.ts
Loading the club data
As we have all the basics out of the way we can finally load
our club list in src/pages/club/index.tsx
Clicking on a club line will cause the browser to navigate to http://localhost:5173/clubs/395152559210943000
which is currently still an empty page, let’s fix that by changing src/pages/club/edit.tsx to
Wow, thats a lot to unpack here.
The first portion is the same as the club details page. We simply load
the club if we don’t have it already.
Next, we create a react-hook-form instance
The errors variable will contain the client side form validation errors
in the format
Next we also store server side errors in a serverErrors state hook.
In addition we set up a reference to the Toast component which we will need to show server side
errors to the user.
Then we define the mutation to update the Club
Here, in onMutate we clear any previous serverErrors, then in
onSuccess we simply show a Toast to the user that
saving was successful.
The onError portion is the most interesting, we parse the server
error response and if it has the correct format, we directly
map the per-attribute server errors into the client form.
So this is where the work from part 1
finally pays off. setError allows us to programmatically set
validation errors into the form.
This is the form submit handler which simply fires off the mutation.
The getFormErrorMessage helper is used to render validation errors directly into the form.
Finally, we add the form via
Here we create the form which calls handleSubmit(doSubmit) upon
sending the form.
The handleSubmit function will first validate the form and only if it
is valid, it will invoke the inner doSubmit method that will trigger
the AJAX request.
Testing server side validation errors
Now if you remember the zod validation we created way back when,
you might notice that there is a discrepancy between the minimum
length in typescript
and the corresponding java code
To be able to produce a server side validation error all we need
to do is to update a club and assign it a clubName that is
only 2 characters long.
The server side validation errors are rendered in-line just like
client side validation errors would.
This concludes part 2 of the tutorial. In part 3 we will create
the same application in Svelte to compare the client side
implementation and find out why the hype around Svelte is justified.
Hi, I'm Rainer. I'm a software engineer based in Salzburg.
You can follow me on
Twitter, see
some of my work on
GitHub, or read
more about me on
my website.