springboot & reactjs #2 | progressive enhancement based on list sorting
This is the second article of a springboot & reactjs article series about server side rendering and progressive enhancement. In the first article we have learned how to render a ReactJS app on the server with nashorn. However, actually it is not really an “app” yet. Currently we just see a static list of awesome products…
Today we will implement the sorting feature that should work with a plain html form submit as well as with a Ajax Request and client side rendering. So the app is progressively enhanced with JavaScript \o/
springboot & reactjs article series
- server side rendering ✅
- progressive enhancement based on list sorting 🆕
- improving developer experience
- lessons learned
While the JavaScript solution with client side rendering results in faster rendering and therefore a better user experience, it also has it’s costs. We have to manage the browser url by ourselves. Furthermore we have to implement the browsers back button feature /o\
But let’s take one step after another…
tl;dr
project source code is available on github
HTML form and server side rendering
Before we can even start thinking about the back button we have to implement the sorting feature. So we create a plain html form first that fires a good old get request.
The ProductFilterItem is a simple input field of type radio(button). For the moment the sorting should only consider one attribute. Therefore a radiobutton group is the way to go. So every input is defined with name="sort"
and a label to increase the clickable area.
In the previous blog the ProductList was the only component to render and therefore the entry in main.js. But the new ProductFilter is not part of the list. The ProductList reacts to the filter parameter set by the user. So we have to create an App container that combines our awesome ProductList and ProductFilter components.
Since we have a container now to combine the ProductList and the ProductFilter components we also have to adjust the global.renderServer
function. First we add a second parameter sortBy
to be able to render the selected radio button. Then we must render the new App container instead of the plain ProductList.
That’s it for the frontend part!
Next we need to extend the backend controller to process the sort
request parameter defined in the ProductFilter form and use it to sort the product list.
Additionally the React#renderProducts
method must be extended, too, of course.
And that’s it with the backend part as well!
Now build the frontend, start the spring boot app, open your browser on http://localhost:8080
and start sorting the awesome product list 🙂
$ npm run build $ ./gradlew bootRun
Enhance the client
So far our awesome product list is fully functional. Let’s recap what we can do now.
We are able to:
- see the awesome product info
- sort the awesome products by name or price
- use the browser’s back and forward button (static site!)
- bookmark every single view
As the next step we want to increase the user experience with AJAX requests and client side rendering. This results in a much quicker feedback for the user as requesting the whole html document.
To fetch data dynamically from the server we have to prevent the native form submit and take over the control by ourselves. React provides lifecycle methods like onClick
, onChange
, onSubmit
, etc. So we simply have to register a handler for the form submit. The handler does nothing but to prevent the native behaviour and to inform the consumer of the ProductFilter about the submit.
You may ask why we are subscribing our submit handler via onSubmit
on the form and not the onClick
hook on the submit button. Well, actually we could listen to the button click. But then we had to keep track of all form data by ourselves… And the html form element already provides all this data as a HTMLFormControlsCollection!
Since we use Reacts API for DOM event handling, we have to render our awesome product list on the client, too. We only have server side rendering at this moment, remember? 😉
So additionally to the global.renderServer
function used by Nashorn we need a second function window.renderClient
that we have to call on the client side (browser) as we will see later in this tutorial.
Next we have to add the initial rendering to the index.html template. Of course, we must call window.renderClient
with the same data as on the server. However, React prints a nice error message on the browser console if the data differs (means the client side rendering would result in another DOM structure as the already existing one).
Back to the Java backend we have to inject the initial product list and the sortBy value into the server side model of the ProductController.java
class. Additionally we add a second endpoint to provide the sorted product list as json.
That’s it!
A form submit now fetches the minimal data from the server and the client takes care about the rendering. Awesome, right? If only this queasy feeling wouldn’t be there… Right… The browser url is not changing anymore /o\ And if it couldn’t be worse… Without url changing we also lost the power of the glorious back button.
Make the back button work again
Okay, at first we should face the browser url. With HTML5 we’ve gained the window.history api which is supported by all modern browsers. Changing the url is as simple as pushing the new state into the history with window.history.pushState.
Next we want to listen to the browsers back and forward buttons. This can be implemented with subscribing to the popstate
event. The subscription is done within componentDidMount
since we only want to subscribe in the browser environment. Constructor
and it’s counterpart componentWillMount
are both called on server side creating the static html markup.
Finally we made it 🙂
Go on, build the frontend, start the spring boot app, open http://localhost:8080 and admire our awesome progressively enhanced product list. Functional without JavaScript and even better with enabled JavaScript.
$ npm run build $ ./gradlew bootRun
What do we have learned so far?
- use plain HTML
<form>
element and enhance it with JavaScript andevent.preventDefault
- use
window.history
andpopstate
event to handle the browser back/forward button on the client - manually rebuilding and reloading the ReactJS app still sucks (autoreload would be cool, right)
The next steps will be
- using webpack to enhance the developer experience
- lessons learned
Stay tuned and keep learning!