4 May 2017
Simulating Asynchronous Events
- Vanilla JS
- Vue
- React
Page refreshes are so 2005. Single page applications (SPA) are the new hotness, and offer a variety of design challenges. One such challenge is letting users know what’s happening while a request (e.g., a form submission) does it thing. This play will give you the fundamentals needed to prototype such situations.
Scenario
Let’s suppose you’re designing a SPA. You want to introduce a pattern where a button that triggers an asynchronous event behind the scenes shows a spinner for the duration of the request.
Rundown
The key here is to understand the “states” in which our button will live.
In its default state, the button will show the text “Submit Form”. In its loading state, the button will hide that text and show a spinner instead.
And so, we can mark up our button accordingly, wrapping the “Submit Form” text in a span tag with the class label, and the spinner in a span tag with the class spinner.
<button class="bg-blue bn white tc h3 fw6 w-100 relative">
<span class="label">Submit Form</span>
<span class="spinner absolute top-1 bottom-1 left-0 right-0">
<!-- fancy SVG spinner goes here -->
</span>
</button>
For simplicity’s sake, we can choreograph the hiding of the label / display of the spinner by toggling the class is-loading on the parent button tag.
button .spinner {
opacity: 0; // Hide the spinner by default
transition: opacity .2s ease-out;
}
button .label {
display: block; // Show the label by default
}
button.is-loading {
.label {
display: none; // Hide the label when button 'is-loading'
}
.spinner {
opacity: 1; // Show the spinner when button 'is-loading'
}
}
Now, we can use Javascript to trigger this choreography on click of our button.
Implementation — Vanilla JS (ES6)
Here’s the game plan:
- Write a function named
showSpinner. The function should do a few things:- Add the
is-loadingclass to our button — kicking off the choreography described above. - Remove the
is-loadingclass after a duration of two seconds - Set the
disabledattribute on our button to true while theis-loadingclass is present, to prevent users from repeatedly triggering the sequence.
- Add the
function showSpinner() {
let button = this
let isEnabled = !button.disabled; // Is the button enabled?
if (isEnabled) { // If so
button.disabled = true // Disable the button
button.classList.toggle('is-loading') // Toggle the class
setTimeout(() => { // And do the following after 2 seconds
button.classList.toggle('is-loading') // Toggle the class
button.disabled = false // Enable the button
}, 2000)
}
}
- Add a
clickevent listener to our button which calls the showSpinner function.
// Thou shalt do my bidding when clicked
button.addEventListener('click', showSpinner)
See the Pen Asynchronous Events - Vanilla JS (ES6) by Matt Rothenberg (@mattrothenberg) on CodePen.
Implementation — Vue JS
Here’s the game plan:
- Construct a Vue instance and add a data property called
loadingto represent our button’s loading state
new Vue({
el: '#app',
data: {
loading: false // Button shouldn't spin by default :)
},
methods: {
// nothing to see here
}
})
- Mark up our button, adding the following:
- A
@clickdirective to which we can eventually attach an event listener - A
:disabledattribute that we can enable/disable the button as a function of the loading state - A
:classdirective so that we can dynamically toggle theis-loadingclass with respect to our loading state
- A
<button
@click="submitForm"
:disabled="loading"
v-bind:class="{'is-loading': loading}"
class="bg-blue bn white tc h3 fw6 w-100 relative">
- Implement the
submitFormmethod and add it to our instance’s methods property. Like in the previous example, this method should add theis-loadingclass and remove it after a duration of two seconds - Fortunately, and thanks to Vue’s two-way data binding, we no longer have to manually set our button’s
disabledattribute since it’s a function of our instance’s state
methods: {
submitForm: function() {
let self = this;
self.loading = true; // set the instance's loading state to true
setTimeout(() => {
self.loading = false; // set it to false
}, 2000) // After two seconds
}
}
See the Pen Asynchronous Events - Vue JS by Matt Rothenberg (@mattrothenberg) on CodePen.
Implementation — React JS
Here’s the game plan:
- Scaffold a React component (using the traditional
classsyntax) to represent our button
class SpinnerButton extends React.Component {
constructor () {
super ()
this.state = { // Similar to Vue's 'data' property
loading: false
}
}
}
- Implement our component’s
rendermethod to appease the React gods. Note that we’re only adding theis-loadingclass when our state’sloadingattribute is truthy.
render () {
let buttonClass
this.state.loading ? // Are you loading?
buttonClass = 'is-loading' : // If so, add 'is-loading' class
buttonClass = ''
return(
<button
disabled={this.state.loading} // Disable if loading
className={"bg-blue bn white tc h3 fw6 w-100 relative " + buttonClass}> // Dynamically add the 'is-loading' class
{<!-- implementation details -->}
</button>
)
}
- Add an
onClickhandler to our button, and implement the function it calls
<button onClick={this.submitForm.bind(this)}></button>
submitForm () {
let self = this
self.toggleSpinner()
setTimeout(() => {
self.toggleSpinner()
}, 2000)
}
toggleSpinner () {
this.setState({
loading: !this.state.loading
})
}
See the Pen Asynchronous Events - React JS by Matt Rothenberg (@mattrothenberg) on CodePen.