Premise
The case of choosing between a SPA and a SSR approach boils down to who owns rendering the presentation. Having the server render it, as it is the case in Turbo, decreases programming complexity a lot. But since you always have to wait for any server response to advance the UI, it can feel sluggish, because of the latency involved.
Let’s do a little bit of napkin math. For example, let’s assume our server lives in Chicago, and the client is located in Amsterdam. According to Google, that’s a linear distance of 6,604 km. So, taking the speed of light into account, the lowest possible latency is 22ms, one way. N.B., that’s assuming a linear connection, no infrastructure (routing) costs involved. I’m going to round it up to 50ms for our example here. Where does that put us?
- User clicks a button -> 50ms until it reaches the server
- Server calculates response -> 100ms (fast)
- Answer is returned -> again 50ms
This amounts to 200ms already, and we assumed a fast server response (no other requests in the queue, optimized DB query) and neglected any time the browser needs to actually update the DOM. As you can see, this simple example already flings us way beyond the “perceived as instant” limit of 100ms.
But here’s the twist: In many cases, we already know the outcome of a user action! And what’s more, we can trustingly assume that the action will succeed. Let’s see what we can make of this.
Starting Point
We start from a simple card with a “favorite” button. This button is enclosed in a <form>
tag like would be the case when using Rails’ button_to
helper:
https://stackblitz.com/edit/optimistic-ui-turbo-8?file=index.html%3AL98
After the button is clicked, the favorite
state is switched on the server, and a <turbo-stream action="refresh"></turbo-stream>
action is returned to reconcile the DOM. To visualize the effect of long latency even better, I have made the server response take 2 seconds:
https://stackblitz.com/edit/optimistic-ui-turbo-8?file=index.js%3AL31
Challenge
The challenge this time is
- to render a
<template>
tag containing a<turbo-stream>
action to replace the favorite button with the inversed state inline, i.e. ready to be swapped out on the client: https://stackblitz.com/edit/optimistic-ui-turbo-8?file=index.html%3AL108. Hint: Make sure to include the{{optimisticSvg}}
template tag, the server will automatically populate it with the correct SVG: https://stackblitz.com/edit/optimistic-ui-turbo-8?file=index.js%3AL25. - to wire up an event listener to
turbo:submit-start
in order to clone and append the<turbo-stream>
inlined in the above step 👆 https://stackblitz.com/edit/optimistic-ui-turbo-8?file=app.js%3AL10
Here’s the outcome:
Caveat
The above will work only once, because due to a bug in Idiomorph, <template>
tags aren’t morphed correctly. We have to prepend a separate turbo stream action for swapping out the template’s content with the inversed one manually: https://stackblitz.com/edit/optimistic-ui-turbo-8?file=index.js%3AL37. Don’t worry, I will detail that in the solution!
Teaser
- How could you generalize this pattern?