How to display a Firestore timestamp field, formatted with date-fns, in a Vue 3 project?

Viewed 564

In a Cloud Firestore document, in a collection titled reviews, I have a field titled createdAt of timestamp type.

I'm trying to display that createdAt field in the DOM using the date-fns date utility library's formatDistanceToNow, which returns the distance between the given date and now in words, such as "less than a minute" ago.

For example, in a given Firestore document, createdAt is of timestamp type with the value 11/14/2021 10:49:09 AM

I am able to access and display the createdAt field, as follows:

<p>{{ review.createdAt }}</p> results in this in the the DOM: Timestamp(seconds=1636904949, nanoseconds=271000000)

<p>{{ review.createdAt.toDate() }}</p> results in this in the the DOM: Sun Nov 14 2021 10:49:09 GMT-0500 (Eastern Standard Time)

I am trying to display the date-fns formatted date as follows:

In the <template> section: <p>{{ computedDateToNow }}</p>

And in the <script> section:

const computedDateToNow = computed(() => {
  return formatDistanceToNow(review.createdAt.toDate())
})

console.log(computedDateToNow)

And the error I'm getting in the console is

Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'toDate')
    at ReactiveEffect.eval [as fn] (ReviewDetails.vue?5986:590)
    at ReactiveEffect.run (reactivity.esm-bundler.js?a1e9:160)
    at ComputedRefImpl.get value [as value] (reactivity.esm-bundler.js?a1e9:1087)
    at unref (reactivity.esm-bundler.js?a1e9:1001)
    at Object.get (reactivity.esm-bundler.js?a1e9:1004)
    at Proxy.render (ReviewDetails.vue?5986:34)
    at renderComponentRoot (runtime-core.esm-bundler.js?5c40:756)
    at ReactiveEffect.componentUpdateFn [as fn] (runtime-core.esm-bundler.js?5c40:4594)
    at ReactiveEffect.run (reactivity.esm-bundler.js?a1e9:160)
    at callWithErrorHandling (runtime-core.esm-bundler.js?5c40:6987)

review.createdAt and review.createdAt.toDate() are displaying just fine in the DOM, between the <p> tags.

Why is the toDate() method (link to that in Firebase docs) causing a problem in computedDateToNow?

UPDATE Based on this comment that "It is very likely that this javascript function was placed before the actual html was loaded" I added an if (review.createdAt) statement and the error goes away, BUT review.createdAt is still undefined in console.log(computedDateToNow)

Here's the code block, with the if statement:

const computedDateToNow = computed(() => {
    if (review.createdAt) {
      console.log('begin new console dot log',review.createdAt,'end new console dot log')
      return formatDistanceToNow(review.createdAt.toDate())
    }
      
    })

ADDED (in response to @Raffobaffo's request):

<script>

import useDocument from '@/composables/useDocument'
import getDocument from '@/composables/getDocument'
import { computed } from 'vue'
import { formatDistanceToNow } from 'date-fns'

export default {
  props: ['id'],
  components: { },
  setup(props) {
    const { error, document: review } = getDocument('reviews', props.id)

    const { deleteDoc, updateDoc } = useDocument('reviews', props.id)


// BEGIN formatting timestamp

console.log('begin new console dot log',review.createdAt,'end new console dot log')

    const computedDateToNow = computed(() => {
    if (review.createdAt) {
      console.log('begin new console dot log',review.createdAt,'end new console dot log')
      return formatDistanceToNow(review.createdAt.toDate())
    }
      
    })

    console.log(computedDateToNow)
    
// END formatting timestamp



    return { error, review, formatDistanceToNow, computedDateToNow }  
  }
}
</script>

Thanks for any help!

2 Answers

What I can suggest is to use the npm package manager:

In that case, you can use filters directly in your component.

// my-component.js
import { dateFilter } from "vue-date-fns";
 
export default {
    filters: {
        date: dateFilter
    }
}

<!-- my-component.vue -->
<template>
    <div>Now: {{ new Date() | date }}</div>
</template>

In case you want to continue working with your current code, maybe you can try to debug your const computeDateToNow, to see what the values you are getting are.

According to the documentation you shared of date-fns, here is an example of console.log:

console.log(format(new Date(), "'Today is a' eeee"))

Output should be:

//=> "Today is a Wednesday"

That will help you to validate if the function is being well executed; if not, validate the import of the library.

One recommendation could be to change the data type from const to var. Remember that var can be updated and re-declared, and const not only cannot be redeclared, but also cannot be reassigned.

A point is on the computed: you are returning a value just if the check passes, while a computed property should always return something.

So I would rather try with this approach:

const computedDateToNow = computed(() => {
if (review.createdAt) {
  console.log('begin new console dot log',review.createdAt,'end new console dot log')
  return formatDistanceToNow(review.createdAt.toDate())
}
  return 'not-available';
})
Related