Skip to content

AJAX

Initiating AJAX requests is facilitated by lifecycle functions:

  • onShow()
  • onMount()
  • onShown()
  • onHide()
  • onHidden()

This allows you to do very powerful things. For example, let's say you wanted to show a new image inside a tooltip each time it gets shown:

Let's walk through a little tutorial to learn how to do this.

First, let's setup our HTML:

<button id="ajax-tippy">Hover for a new image</button>

Now, let's add some JavaScript:

tippy('#ajax-tippy', {
  content: 'Loading...',
  animateFill: false,
  animation: 'fade',
  // This option is recommended if your tooltip changes size while showing
  flipOnUpdate: true,
})

Here's the result so far:

To initiate the AJAX request every time the tippy shows, use the onShow lifecycle:

tippy('#ajax-tippy', {
  content: 'Loading...',
  animateFill: false,
  animation: 'fade',
  flipOnUpdate: true,
  onShow(instance) {
    // Code here is executed every time the tippy shows
  },
})

Modern browsers support the fetch API, so we'll use it for this example because it's cleaner than XMLHttpRequest. We are using an Unsplash API to fetch a random 200x200 image:

tippy('#ajax-tippy', {
  // ...
  onShow(instance) {
    fetch('https://unsplash.it/200/?random')
      .then(response => response.blob())
      .then(blob => {
        // Convert the blob into a URL
        const url = URL.createObjectURL(blob)
        // Create an image
        const image = new Image()
        image.width = 200
        image.height = 200
        image.style.display = 'block'
        image.src = url
        // Update the tippy content with the image
        instance.setContent(image)
      })
  },
})

There are currently two problems with this:

  • When the tippy is hidden, it doesn't reset back to Loading...
  • If you quickly hover in and out of the tippy, it initiates many different requests and each image rapidly replaces the old one as each request finishes.

The first one can be solved by using the onHidden lifecycle, which is executed once the tippy fully transitions out and is unmounted from the DOM:

tippy('#ajax-tippy', {
  // ...
  onHidden(instance) {
    instance.setContent('Loading...')
  },
})

The second one is trickier and requires using state. This will let us know the current condition the tooltip is in.

tippy('#ajax-tippy', {
  // ...
  onShow(instance) {
    // We can monkey-patch the instance's state object with our own state
    if (instance.state.ajax === undefined) {
      instance.state.ajax = {
        isFetching: false,
        canFetch: true,
      }
    }

    // Now we will avoid initiating a new request unless the old one
    // finished (`isFetching`).
    // We also only want to initiate a request if the tooltip has been
    // reset back to Loading... (`canFetch`).
    if (instance.state.ajax.isFetching || !instance.state.ajax.canFetch) {
      return
    }

    fetch('https://unsplash.it/200/?random')
      .then(response => response.blob())
      .then(blob => {
        // ...
      })
      .catch(error => {
        // ...
      })
      .finally(() => {
        // Make sure to set it back to false so new requests can be
        // initiated
        instance.state.ajax.isFetching = false
      })
  },
  onHidden(instance) {
    instance.setContent('Loading...')
    instance.state.ajax.canFetch = true
  },
})

We use state to keep track of the request's status. Without the state booleans, unexpected effects may occur, such as initiating too many requests before waiting for the previous one to finish, or showing new images before the tooltip hides and resets back to Loading..., both of which appear "buggy".

Currently, the image instantly replaces the Loading... text without any smooth transition. How do we make it so the tooltip smoothly transitions in height?

To look at example code solving this dynamically (i.e. not knowing the height of the image or initial size of the tooltip) view the CodePen demo.