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…
springboot & reactjs article series
- server side rendering ✅
- progressive enhancement based on list sorting 🆕
- improving developer experience
- lessons learned
But let’s take one step after another…
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.
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
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.
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 🙂
$ npm run build $ ./gradlew bootRun
What do we have learned so far?
- use plain HTML
popstateevent 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!