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-loading
class to our button — kicking off the choreography described above. - Remove the
is-loading
class after a duration of two seconds - Set the
disabled
attribute on our button to true while theis-loading
class 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
click
event 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
loading
to 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
@click
directive to which we can eventually attach an event listener - A
:disabled
attribute that we can enable/disable the button as a function of the loading state - A
:class
directive so that we can dynamically toggle theis-loading
class 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
submitForm
method and add it to our instance’s methods property. Like in the previous example, this method should add theis-loading
class 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
disabled
attribute 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
class
syntax) 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
render
method to appease the React gods. Note that we’re only adding theis-loading
class when our state’sloading
attribute 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
onClick
handler 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.