Day 13: Flashing tapped button while user is waiting (with React and Styled Components)
Tags: styledcomponents, react, cssanimation, webdev
Published: 2021-10-26 07:20:06

## TL;DR

Sometimes it takes a while for web apps to show the result after the user taps a button. One way to tell the user that the app _is_ doing hard work (rather than being frozen) is to flash the tapped button while the user is waiting for the result **(see Section 1 for detail)**.

To implement this feature with React and Styled Components:

1. Create a state variable with the `useState()` hook. Set its initial value to be `initial`. Once the button is clicked, set it to be `loading`. Then switch it to another value once the result is shown **(see Section 2 for detail)**.
2. Attach an attribute called `data-loading` to the `<button>` element, and toggle its value to `true` when the state variable takes the value of `loading`. Then, use the attribute selector `[data-loading="true"]` to style the animation to flash the button. This approach is more performant than using `props` with Styled Components **(see Sections 3 and 4 for detail)**.

## Introduction

This article is a sequel to [**Day 12 of this blog post series**](https://dev.to/masakudamatsu/day-12-showing-user-location-on-embedded-google-maps-with-geolocation-api-and-react-128j), where I described how I wrote the React code so that tapping a button would show the user's location on embedded Google Maps. 

After tapping the button, however, it can take a few seconds until the user sees their location on the map. So it's best to tell the user that the app is working hard to get the user's location data. Otherwise, the user will wonder if tapping the button does anything to the app. Using the phrase coined by UX design guru [Norman (2013)](https://www.nngroup.com/books/design-everyday-things-revised/), we need to bridge the “[gulf of evaluation](https://en.wikipedia.org/wiki/Gulf_of_evaluation)”.

A solution I've chosen is to make the button start flashing after the button is tapped, and then to stop blinking it once the user's location is shown on the map.

This article describes how I've implemented this solution for _My Ideal Map App_, a web app I'm building to improve the user experience of Google Maps (see [**Day 1 of this blog series**](https://dev.to/masakudamatsu/creating-saas-to-solve-my-own-problem-4gk9) for more detail on _My Ideal Map App_).

## 1. Why flashing the button?

### 1.1 In line with design concept

Flashing light is used to signal something. Lighthouses flash on and off to send a message to ships off shore. Drivers flash their headlights to send a message to other drivers. Somehow, flashing light is associated with transportation.

Showing the user's location on the map is like the user flying up into the sky and looking down below (which is part of the design concept of _My Ideal Map App_; see [**Day 2 of this blog series**](https://dev.to/masakudamatsu/day-2-finding-design-concept-to-drive-ui-design-process-49g3)). That's why I use the flight takeoff icon as the button label for showing the user location (see Section 1.3 of [**Day 8 of this blog series**](https://dev.to/masakudamatsu/day-8-positioning-buttons-over-embedded-google-maps-3d5b)). When I see this button flashing on and off, it somehow feels right. Perhaps because flashing light is associated with transportation in general.

### 1.2 Why not other solutions?

There are other solutions to indicate that the app is currently working hard. One option is a loading indicator, like an animated hourglass icon. Another option is a temporary banner message shown at the bottom of the screen (i.e., what Google's Material Design calls a “[snackbar](https://material.io/components/snackbars)”). However, _My Ideal Map App_ embeds Google Maps *full-screen*. Any additional UI element will prevent the user from seeing some parts of the map while they're waiting for their location to be shown. Maybe the user notices something interesting on the map while they're waiting, and want to check that out afterwards. I don't want the user to miss such an opportunity for discovery.

Rather than adding something to the screen, therefore, it's better to animate the button that the user just tapped. It clearly connects the user's action (tapping the button) to the app's response to it.

What kind of animation, then? The web app version of Google Maps uses a rotating circle on the button to tap for showing the user location. To differentiate from Google Maps, therefore, animating the button label is not an option (_My Ideal Map App_ aims to _improve_ Google Maps, not to copycat it).

Which is why I've chosen to animate the entire button, rather than the button label only. And flashing the button echoes the design concept of _My Ideal Map App_, as described above.

## 2. How to implement with React

### 2.1 Settings

I'm using [Next.js](https://nextjs.org/) to build _My Ideal Map App_, and Next.js relies on [React](https://reactjs.org/) to compose the user interface (UI). 

And here's the overall structure of React code for showing the user location after the user taps a button. Read comments inserted to learn what each line of code does (for more detail, see [**Day 12 of this blog post series**](https://dev.to/masakudamatsu/day-12-showing-user-location-on-embedded-google-maps-with-geolocation-api-and-react-128j)):

```jsx
// Create a button component that takes Google Maps instance as a prop
const LocatorButton = ({mapObject}) => {
  // Define the function to run when the user taps the button
  const getUserLocation = () => {
    // Check if the user's browser supports Geolocation API
    if (navigator.geolocation) {
      // Obtain user location data from user's device
      navigator.geolocation.getCurrentPosition(position => {
        // Store user location data
        const userLocation = {
          lat: position.coords.latitude,
          lng: position.coords.longitude,
        };
        ...
        // Insert code for marking the user location on the map
        ...
        // Snap the map to the user location
        mapObject.setCenter(userLocation);
      });
    } else {
      // Insert code for legacy browsers not supporting Geolocation API
    }
  };
  return (
    <button
      // run getUserLocation function upon tapping the button
      onClick={getUserLocation} 
      type="button"
    >
      {/* Insert HTML for button label icon */}
    </button>
  );
};
```

Now I'm going to revise the above code to flash the button.

### 2.2 Defining a state variable

Making a button start flashing is a change in the UI. With React being used to build an app, a change in the UI is implemented with the [React state](https://reactjs.org/docs/state-and-lifecycle.html), the change of which triggers the re-rendering of a UI component (and its child components).

So I first define a variable called `status` which will store the UI status of the `<LocatorButton>` component and a method `setStatus` to update the UI status (by changing the value of the `status` variable):

```jsx
import {useState} from 'react'; // ADDED

const LocatorButton = ({mapObject}) => {
  const [status, setStatus] = useState('initial'); // ADDED
  const getUserLocation = () => {
    ...
  };
  ...
};
```

where the initial value of `status` is literally set to be `initial`.

### 2.3 Updating the state variable

Then when the user clicks the button, I switch the value of `status` to `loading`; once the user's location is shown on the map, I switch the value of `status` to `watching`:

```jsx
import {useState} from 'react';

const LocatorButton = ({mapObject}) => {
  const [status, setStatus] = useState('initial');
  const getUserLocation = () => {
    if (navigator.geolocation) {
      setStatus('loading'); // ADDED
      navigator.geolocation.getCurrentPosition(position => {
        const userLocation = {
          lat: position.coords.latitude,
          lng: position.coords.longitude,
        };
        ...
        // Insert code for marking the user location on the map
        ...
        mapObject.setCenter(userLocation);
        setStatus('watching'); // ADDED
      });
    } else {
      // Insert code for legacy browsers not supporting Geolocation API     
    }
  };
  ...
};
```

### 2.4 Changing the style applied to the button

To make the button flash while the `status ` takes the value of `loading`, I add an attribute called `data-loading` to the `<button>` element and set its value to whether the expression `status === "loading"` is `true` or `false`:

```jsx
    <button
      data-loading={status === "loading"} // ADDED
      onClick={getUserLocation}
      type="button"
    >
      {/* Insert HTML for button label icon */}
    </button>
```

Then I will style the button with the data attribute selector (see Sections 3 and 4 below).

You may wonder why I don't use `className` instead. That's because I'm using CSS-in-JS (more specifically, [Styled Components](https://styled-components.com/)) to style HTML elements. See Section 4 below for more detail.

## 3. Define animation

### 3.1 CSS code

Here's “vanilla” CSS code for flashing the `<button>` element while its `data-loading` attribute is `true`:

```css
@keyframes flashing {
  0% {
    opacity: 1;
  }
  50% {
    opacity: 0;
  }
  100% {
    opacity: 1;
  }  
}

button[data-loading="true"] {
  animation: flashing 1500ms linear infinite;
}
```

This CSS code is adapted from [Fayock (2020)](https://www.freecodecamp.org/news/make-it-blink-html-tutorial-how-to-use-the-blink-tag-with-code-examples/). To understand what it means, it's best to start from the [`animation ` property](https://css-tricks.com/almanac/properties/a/animation/). It sets the duration of animation to be 1.5 seconds (`1500ms`) and the speed of animation to be constant (`linear`), with animation repeated as long as the `data-loading` attribute is `true`  (`infinite`).

The `flashing` refers to how the button's style changes during each run of the 1.5 second-long animation. It starts with the opacity of 100%, that is, the button is shown solid. During the first half of the 1.5 seconds of animation, the opacity decreases steadily to 0% so that the button slowly disappears. During the second half of the 1.5 seconds, however, the opacity increases steadily from 0% to 100% so that the button slowly reappears. 

---

Why do I choose the duration of 1.5 seconds and the constant speed of animation? UI designers should be able to explain why they choose particular values of animation duration and speed changes (known as _easing_). Here's the rationale behind my design decisions.

### 3.2 Rationale for duration

For duration, I choose 1.5 seconds. Even though more than 0.5 second is considered to be too long for UI animation ([Head 2016](https://valhead.com/2016/05/05/how-fast-should-your-ui-animations-be/)), even the duration of 1 second feels too fast for this particular case. 

I guess the flight takeoff icon makes me imagine the airplane slowly moving on the runway to get ready for takeoff. A fast-flashing button appears incongruent to this imaginary takeoff.  

Trying various lengths of duration over 1 second, I find 1.5 seconds to strike the right balance between too fast and too slow.

### 3.3 Rationale for easing

For easing, I choose `linear`. My guideline for choosing the easing pattern is to think of real-life counterparts. [Liew (2017)](https://zellwk.com/blog/css-transitions/) first enlightened me about it. He says:

> Imagine yourself throwing a tennis ball into an open field. The ball leaves your hand with the maximum speed. As it moves, it loses energy, it decelerates and eventually comes to a halt. ... Now imagine you’re in a car. It’s not moving right now. When you move the car, it accelerates and goes toward its top speed. 

If animation is something equivalent of the movement triggered by human body movement (e.g., animation triggered by the user's swipe of the screen), we should make animation speed starting fast and then slowing down. If it's like the movement initiated by a machine (e.g., animation triggered by pressing a button), animation speed should start slow and then accelerate.

For flashing light, however, there is no movement of physical objects involved. If so, it's natural to keep the animation speed constant. This is also a recommendation by [Skytskyi (2018)](https://uxdesign.cc/the-ultimate-guide-to-proper-use-of-animation-in-ux-10bd98614fa9):

> Linear motion can, for example, be used only when the object changes its color or transparency. Generally speaking, we can use it for the states when an object does not change its position.

So I go with linear motion.

## 4. How to implement with Styled Components

### 4.1 Setting up

To use Styled Components to style the `<button>` element, I refactor the React code in Section 2 above by replacing `<button>` with  `<Button>`:

```jsx
...
import {Button} from './Button.js'; // ADDED

const LocatorButton = ({mapObject}) => {
  ...
  return (
    <Button // REVISED
      data-loading={status === "loading"}
      onClick={getUserLocation}
      type="button"
    >
      {/* Insert HTML for button label icon */}
    </Button> {/* REVISED */}
  );
};
```

Then define the `Button` styled component in a separate file called `Button.js` (by separate a file for styling with CSS from the one for behavior with JavaScript, we can immediately tell where to look in the code base for each purpose):

```js
// Button.js
import styled from 'styled-components';

const styleButton = `
  /* Insert CSS declarations for styling the button */  
`;

export const Button = styled.button`
  ${styleButton}
`;

```

Instead of writing CSS declarations directly into the `Button` styled component, I first define a variable that contains a string of CSS declarations to achieve one purpose and then refer to it inside the styled component. This way, I can effectively add a “comment” on what each set of CSS declarations achieves (which is often hard to tell from the code itself). I try to avoid inserting the standard comments to the code as much as possible, because I'm sure I will forget updating them when I change the code in the future.

For more detail on how I've styled the button, see [**Day 7**](https://dev.to/masakudamatsu/day-7-making-buttons-look-like-clouds-for-embedded-google-maps-33po) and [**Day 8**](https://dev.to/masakudamatsu/day-8-positioning-buttons-over-embedded-google-maps-3d5b) of this blog series.

### 4.2 Animating the button

To add the CSS code for animating the button as described in Section 3 above, we first need to use the `keyframes` helper function to define how animation proceeds:

```javascript
import styled, {keyframes} from 'styled-components'; // REVISED

const styleButton = `
  /* Insert CSS declarations for styling the button */  
`;

// ADDED FROM HERE
const flashing = keyframes`
  0% {
    opacity: 1;
  }
  50% {
    opacity: 0;
  }
  100% {
    opacity: 1;
  }
`;
// ADDED UNTIL HERE

export const Button = styled.button`
  ${styleButton}
`;
```

Then, set the `animation` property with Styled Components's `css` helper function:

```javascript
import styled, {css, keyframes} from 'styled-components'; // REVISED

const styleButton = `
  /* Insert CSS declarations for styling the button */  
`;

const flashing = keyframes`
  0% {
    opacity: 1;
  }
  50% {
    opacity: 0;
  }
  100% {
    opacity: 1;
  }
`;

// ADDED FROM HERE
const flashButton = css`
  &[data-loading="true"] {
    animation: ${flashing} 1500ms linear infinite;
  }
`;
// ADDED UNTIL HERE

export const Button = styled.button`
  ${styleButton}
  ${flashButton} /* ADDED */
`;
```

 We need to use the `css` helper function; otherwise Styled Components cannot tell what `flashing` refers to (see [Styled Components documentation](https://styled-components.com/docs/basics#animations)).

This way, the button will be flashing only when the `data-loading` attribute takes the value of `true`, that is, when the app is searching for the user on the map. 

In case you've been using Styled Components a lot and wonder why I don't use `props` in place of the data attribute selector, it's for performance reasons. See [Arvanitakis (2019)](https://calendar.perfplanet.com/2019/the-unseen-performance-costs-of-css-in-js-in-react-apps/) for why `props` is bad for performance (see also Section 3.4 of [**Day 8 of this blog series**](https://dev.to/masakudamatsu/day-8-positioning-buttons-over-embedded-google-maps-3d5b)).

## Demo

With the code explained in this article (and the previous article), I've uploaded [a demo app](https://4aa36604.mima.pages.dev/) to Cloudflare Pages. Try to click the button (when asked for permission to use location services, answer yes). You'll see the button flashing until your location is shown on the map.

If you notice something weird, file a bug report by posting a comment to this article. I'll apprecaite your help to improve _My Ideal Map App_! ;-)

## Next step

If _My Ideal Map App_ were a desktop app, it would be good enough to show the user's location each time the user clicks the button. However, the app is also meant to be used with a smartphone while the user is moving around in a city. It's more desirable for the app to keep track of the user location, updating the marker constantly. Next step is to implement such a feature.

## Reference

Arvanitakis, Aggelos (2019) “[The unseen performance costs of modern CSS-in-JS libraries in React apps](https://calendar.perfplanet.com/2019/the-unseen-performance-costs-of-css-in-js-in-react-apps/)”, *Web Performance Calendar*, Dec 9, 2019.

Fayock, Colby (2020) “[Make It Blink HTML Tutorial – How to Use the Blink Tag, with Code Examples](https://www.freecodecamp.org/news/make-it-blink-html-tutorial-how-to-use-the-blink-tag-with-code-examples/)”, _FreeCodeCamp_, Jul 27, 2020.

Head, Val (2016) “[How fast should your UI animations be?](https://valhead.com/2016/05/05/how-fast-should-your-ui-animations-be/)”, _valhead.com_, May 5, 2016.

Liew, Zell (2017) “[CSS Transitions explained](https://zellwk.com/blog/css-transitions/)”, _zellwk.com_, Dec 13, 2017.

Norman, Don (2013) [_The Design of Everyday Things_](https://www.nngroup.com/books/design-everyday-things-revised/_), revised and expanded edition, New York: Basic Books.

Skytskyi, Taras (2018) “[The ultimate guide to proper use of animation in UX](https://uxdesign.cc/the-ultimate-guide-to-proper-use-of-animation-in-ux-10bd98614fa9)”, _UX Collective_, Sep 5, 2018.