DEV Community

Gohomewho
Gohomewho

Posted on • Updated on

Improve User Experience with a Minimal Loading State: A Vue Composable for Making Delay Ref

Fetching resources and displaying a loading UI is a common task when building Vue applications. One simple approach is to use v-if="data" and v-else to toggle the UI, but this can become problematic if the APIs you're working with respond too quickly. In such cases, the loading UI may flash briefly and distract users, creating a poor user experience.

While working recently, I encountered a situation where the APIs I was using became extremely fast after a colleague moved them to AWS. As a result, the flashing of the loading UI became too noticeable and disruptive for users. To solve this problem, I created a composable.

What is a composable

In the context of Vue applications, a "composable" is a function that leverages Vue's Composition API to encapsulate and reuse stateful logic.


My implementation

My initial plan was to create a ref exclusively for tracking the loading state. However, I thought that it would be more versatile if we could manipulate its value instead of merely using a boolean.

export function refMinDelay(
  sourceRef,
  delay,
  conditionToDoSomethingImmediate = newValue => newValue === true,
  doImmediate = newValue => true,
  doAfterDelay = newValue => false,
) {
  let timeId = 123
  const delayRef = ref(sourceRef.value)

  watch(sourceRef, () => {
    if (conditionToDoSomethingImmediate(sourceRef.value) === true) {
      clearTimeout(timeId)

      delayRef.value = doImmediate(sourceRef.value)

      timeId = setTimeout(() => {
        timeId = null

        if (conditionToDoSomethingImmediate(sourceRef.value) === true)
          return

        delayRef.value = doAfterDelay(sourceRef.value)
      }, delay)
    }
    else {
      if (timeId)
        return

      delayRef.value = doAfterDelay(sourceRef.value)
    }
  }, {
    immediate: true,
  })

  return delayRef
}
Enter fullscreen mode Exit fullscreen mode

The refMinDelay function takes in a sourceRef, a delay time in milliseconds, and optional parameters conditionToDoSomethingImmediate, doImmediate, and doAfterDelay. It returns a reactive delayRef that is updated based on the sourceRef updates and the specified delay.

If the sourceRef updates and the conditionToDoSomethingImmediate function evaluates to true, the delayRef will be immediately updated to the return value of the doImmediate function. If conditionToDoSomethingImmediate evaluates to false, the delayRef will only update to the return value of the doAfterDelay function if the specified delay has passed. if the delay has not yet passed, the update will be deferred until the minimal delay is up.

If sourceRef is a boolean loading state, the default values for conditionToDoSomethingImmediate, doImmediate, and doAfterDelay are provided to create a delay loading state. For example, refMinDelay(loading, 500) will create a delay loading state with a 500ms delay.


When to use

When fetching data and displaying a loading UI, frequent switches between the loading UI and the data display can cause the screen to flash, which can disrupt the user's experience. To create a smoother transition, a minimal loading state can be used to delay the switch from the loading UI to the data display until a minimal delay has passed, ensuring that the loading state is visible for at least that duration.


How to use

Create a delay loading state from an existing loading state.

const loading = ref(false)
const delayLoading = refMinDelay(loading, 500)
Enter fullscreen mode Exit fullscreen mode

Create a delay loading state from a prop.

const data = computed(() => props.data)
const delayLoading = refMinDelay(data, 500, (value) => value == null)
Enter fullscreen mode Exit fullscreen mode

Create a delay state of the source.

const source = ref({ id: 1})
const delay = refMinDelay(
  source,
  500,
  (v) => v == null, /* condition to set value immediately */
  (v) => v, /* set value immediately  */
  (v) => v /* set value once minimal delay is up*/
);
Enter fullscreen mode Exit fullscreen mode

Example

You can play the example on stackblitz or use the embedded version below. Click the +1 button at different speeds and adjust the delay values for both delayLoading and responseTime to see different results.

To learn how refMinDelay works, right-click an element and select "inspect", go to the console tab, and check what's logged from refMinDelay.js.

If the API is very fast, the loading UI will still be shown until the minimum delay time has elapsed. For example, responseTime takes 100ms and delayLoading is set to 300ms.

as description

If the API takes longer than the delay to respond, there will be no additional delay. For example, responseTime takes 500ms and delayLoading is set to 300ms.

as description

Top comments (1)

Collapse
 
connerthedev profile image
conner

Great write up! Was planning to implement something similar to this today and just randomly stumbled across this post