Updating relative time in UI with Nuxt3

I bumped into this problem when working on a side project - aihairstyles.com. When users generate a new set of styles, I want to show them when that set was generated, relative to now. I also want that time to update dynamically as time goes on, without the need for them to refresh the page. Finally, I want to use this logic throughout my app in lots of places.

I solved it with a combination of global state, date-fns library and a small plugin I wrote.

1. Create a state variable to hold a "ticker"

In my useState.ts file, I'm adding a variable that we'll update later via a plugin.

export const useTicker = () => useState<any>('ticker', () => new Date().getTime())

2. Create a plugin to update this ticker at a given interval


// globalTicker.client.ts

export default defineNuxtPlugin(nuxtApp => {

  // updating a global ticker in useState that can be used to auto-update times in the UI

  const ticker = useTicker()
  
  const TICKER_INTERVAL = 30000; // milliseconds

  setInterval(() => {
    let newVal = new Date().getTime();
    ticker.value = newVal
  }, TICKER_INTERVAL);
  
})

With this plugin, every 30 seconds the ticker will update (30000 milliseconds).

3. Install date-fns library (or a lib of your choosing)

I used to use moment.js, but date-fns is my go-to now as it's smaller and can be tree-shaken. But use whatever makes you happy.

npm install date-fns --save

4. Create a composable that exports a relative time function

As I mentioned, I wanted this logic throughout my app in lots of places, so creating a composable was the way to go.

To do this, I created a file called useDateHelpers.ts in my /composables directory.

// /composables/useDateHelpers.ts

import formatDistance from 'date-fns/formatDistance'

export const relativeDate = (date:string) => {
  console.log("date:", date)
  const ticker = useTicker()
  return formatDistance(new Date(date), ticker.value)
}

Notice we're importing our global ticker and using it, making our function's return value reactive in Vue's eyes

5. Finally, use the function in our components

Then, in the html, we use this function and pass in the created_date and the global ticker variable. In the code below we have a for-loop that loops over all image sets.

<!-- rest of preview page ) -->
    
<h2 class="gradient-text-1 text-2xl">
	{{preview.data.style}}
</h2>
<p class="text-sm muted mb-3">
    {{ relativeDate(preview.data.result_data?.created_at)}} ago
</p>

<!-- rest of preview page -->

The result? We have relative time updating in our UI

Your browser does not support the video tag.

Notice the "less than a minute ago" updates to "1 minute ago" as we update the global ticker (see console logs).

Any suggestions for improvement? Let me know :)