15

I am trying to figure out how to display a firestore timestamp in a react app.

I have a firestore document with a field named createdAt.

I am trying to include it in a list of output (extracting the relevant bits here so that you don't have to read through the entire list of fields).

componentDidMount() {
    this.setState({ loading: true });

    this.unsubscribe = this.props.firebase
      .users()
      .onSnapshot(snapshot => {
        let users = [];

        snapshot.forEach(doc =>
          users.push({ ...doc.data(), uid: doc.id }),
        );

        this.setState({
          users,
          loading: false,
        });
      });
  }

  componentWillUnmount() {
    this.unsubscribe();
  }

  render() {
    const { users, loading } = this.state;

    return (
        <div>
    {loading && <div>Loading ...</div>}

            {users.map(user => (

                <Paragraph key={user.uid}>  

       <key={user.uid}>  
       {user.email}
       {user.name}
       {user.createdAt.toDate()}
       {user.createdAt.toDate}
       {user.createdAt.toDate()).toDateString()}

The only attribute that won't render is the date.

Each of the above attempts generates an error that says:

TypeError: Cannot read property 'toDate' of undefined

I have seen this post, and this post and this post, and this post and others like them, which suggest that toDate() should work. But - this extension throws an error for me - including when I try the toString extension.

I know it knows there is something in firestore because when I try user.createdAt, I get an error saying it found an object with seconds in it.

Taking the example from Waelmas below, I tried to log the output of the field as:

this.db.collection('users').doc('HnH5TeCU1lUjeTqAYJ34ycjt78w22').get().then(function(doc) {
  console.log(doc.data().createdAt.toDate());

}

I also tried adding this to my map statement but get an error that says user.get is not a function.

{user.get().then(function(doc) {
                    console.log(doc.data().createdAt.toDate());}
                  )}

It generates the same error message as above.

enter image description here

NEXT ATTEMPT

One strange thing that has come up in trying to find a way to record a date in Firestore that allows me to then read it back, is that when I change my submit handler in one form to use this formulation:

handleCreate = (event) => {
    const { form } = this.formRef.props;
    form.validateFields((err, values) => {
      if (err) {
        return;
      };
    const payload = {
    name: values.name,
    // createdAt: this.fieldValue.Timestamp()
    // createdAt: this.props.firebase.fieldValue.serverTimestamp()

    }
    console.log("formvalues", payload);
    // console.log(_firebase.fieldValue.serverTimestamp());


    this.props.firebase
    .doCreateUserWithEmailAndPassword(values.email, values.password)
    .then(authUser => {
    return this.props.firebase.user(authUser.user.uid).set(
        {
          name: values.name,
          email: values.email,
          createdAt: new Date()
          // .toISOString()
          // createdAt: this.props.firebase.fieldValue.serverTimestamp()

        },
        { merge: true },
    );
    // console.log(this.props.firebase.fieldValue.serverTimestamp())
    })
    .then(() => {
      return this.props.firebase.doSendEmailVerification();
      })
    // .then(() => {message.success("Success") })
    .then(() => {
      this.setState({ ...initialValues });
      this.props.history.push(ROUTES.DASHBOARD);

    })


  });
  event.preventDefault();
    };

That works to record a date in the database.

The form of the firestore entry looks like this:

enter image description here

I'm trying to display a date in this component:

class UserList extends Component {
  constructor(props) {
    super(props);

    this.state = {
      loading: false,
      users: [],
    };
  }

  componentDidMount() {
    this.setState({ loading: true });

    this.unsubscribe = this.props.firebase
      .users()
      .onSnapshot(snapshot => {
        let users = [];

        snapshot.forEach(doc =>
          users.push({ ...doc.data(), uid: doc.id }),
        );

        this.setState({
          users,
          loading: false,
        });
      });
  }

  componentWillUnmount() {
    this.unsubscribe();
  }

  render() {
    const { users, loading } = this.state;

    return (
      <div>
          {loading && <div>Loading ...</div>}

          <List
            itemLayout="horizontal"
            dataSource={users}

            renderItem={item => (
              <List.Item key={item.uid}>
                <List.Item.Meta
                  title={item.name}
                  description={item.organisation}
                />
                  {item.email}
                  {item.createdAt}
                  {item.createdAt.toDate()}
                  {item.createdAt.toDate().toISOString()}

              </List.Item>
            // )
          )}
          />

      </div>
    );
  }
}

export default withFirebase(UserList);

When I try to read it back - using:

{item.email}

The error message reads:

Error: Objects are not valid as a React child (found: Timestamp(seconds=1576363035, nanoseconds=52000000)). If you meant to render a collection of children, use an array instead. in Item (at UserIndex.jsx:74)

When I try using each of these attempts:

{item.createdAt}
{item.createdAt.toDate()}
{item.createdAt.toDate().toISOString()}

I get an error that says:

TypeError: Cannot read property 'toDate' of undefined

Based on the ability to read back entries logged in the same document in other fields, I'm expecting any of these to work to produce output -even if it's not formatted in the way I want. That doesn't happen.

NEXT ATTEMPT

Taking Waelmas' example, I tried to follow the instructions, but where we aren't getting the same response is in the first step. Where Walemas gets an output based on the .toDate() extension, I get an error saying toDate() is not a function.

Consistent with the Firebase documentation, I tried:

    const docRef = this.props.firebase.db.collection("users").doc("HnH5TeCU1lUjeTqAYJ34ycjt78w22");

docRef.get().then(function(docRef) {
    if (doc.exists) {
        console.log("Document createdAt:", docRef.createdAt.toDate());

} })

This produces a string of errors with the syntax and I can't find a way around them.

NEXT ATTEMPT

I then tried making a new form to see if I could explore this without the authentication aspect of the users form.

I have a form that takes input as:

this.props.firebase.db.collection("insights").add({
            title: title,
            text: text,
            // createdAt1: new Date(),
            createdAt: this.props.firebase.fieldValue.serverTimestamp()
        })

Where in the previous form, the new Date() attempt worked to record a date in the database, in this example both fields for createdAt and createdAt1 generate the same database entry:

enter image description here

<div>{item.createdAt.toDate()}</div>
                    <div>{item.createdAt.toDate()}</div>

When I try to output the value of the dates, the first one generates an error that says:

Objects are not valid as a React child (found: Sun Dec 15 2019 21:33:32 GMT+1100 (Australian Eastern Daylight Time)). If you meant to render a collection of children, use an array instead

The second generates the error saying:

TypeError: Cannot read property 'toDate' of undefined

I'm stuck for ideas on what to try next.

I saw this post that suggests that the following might do something useful:

                {item.createdAt1.Date.valueOf()}

It doesn't. It renders an error that says:

TypeError: Cannot read property 'Date' of undefined

This post seems to be having the same trouble as me, but doesn't go into how they managed to display the date value they stored.

This post seems to be getting stuck at the array error message, but seems to have figured out how to display a date using createdAt.toDate()

Mel
  • 2,481
  • 26
  • 113
  • 273

7 Answers7

18

After some discussion, we found that the time stamps in OPs user object could be renders as such:

render() { 
const { users, loading } = this.state; 

return ( 
    <div> 
        {loading && <div>Loading ...</div>} 

        {users.map(user => ( 

            <Paragraph key={user.uid}> 

                <key={user.uid}> 
                    {user.email} 
                    {user.name} 
                    {new Date(user.createdAt.seconds * 1000).toLocaleDateString("en-US")}

I recreated your example in a dummy React project, and received the same error as expected.

Error: Objects are not valid as a React child

I was able to get this to render correctly with the following method, which should also work for you:

{new Date(user.createdAt._seconds * 1000).toLocaleDateString("en-US")}

Which, for my sample timestamp, rendered as:

12/30/2019


Be sure you are using a timestamp that was saved to Firestore as:

createdAt: this.props.firebase.Timestamp.fromDate(new Date())

Note: This is assuming that your instance of firebase.firestore() is at this.props.firebase. In other examples you use this.props.firebase, but those methods look like helper methods that you have created yourself.

When this value is fetched, it will be an object with two properties -- _seconds and _nanoseconds.

Be sure to include the underscore. If you use createdAt.seconds it won't work, it must be createdAt._seconds.


Other things I tried:

user.createdAt.toDate() throws toDate() is not a function.

user.createdAt throws Error: Objects are not valid as a React child

new Date(user.createdAt._nanoseconds) renders the wrong date

Michael Rodriguez
  • 2,142
  • 1
  • 9
  • 15
  • Hi - thanks for the suggestion. I tried this and get an error that says: TypeError: Cannot read property 'Timestamp' of undefined – Mel Dec 31 '19 at 04:33
  • I also tried: createdAt: firebase.firestore.fieldValue.serverTimestamp().fromDate(new Date()), which returns an error that says: TypeError: Cannot read property 'fieldValue' of undefined – Mel Dec 31 '19 at 04:39
  • Although this didn't work for me, I'm interested to understand how you came to think to try the format you've used. It's not as shown in the documentation. If I can learn how you discovered something that works for you, it might let me find a way that works for me (I don't understand why those aren't the same thing for everyone - but I need new ideas for things to try). Thanks again for trying to help. – Mel Dec 31 '19 at 04:41
  • What I can do is save a date (as shown above) using: createdAt: this.props.firebase.fieldValue.serverTimestamp(), – Mel Dec 31 '19 at 04:54
  • However, when I try to print that date back on the page, I tried: description={ new Date(item.createdAt._seconds * 1000).toLocaleDateString("en-US") } - which produces an output that reads "Invalid Date" - no error message is generated. – Mel Dec 31 '19 at 04:55
  • Hi, no problem. So `TypeError: Cannot read property 'Timestamp' of undefined` makes sense, it was my mistake. In my example `firebase` is my instance of firebase in my example. I should have written this in the context of your example. I'll update this now. – Michael Rodriguez Dec 31 '19 at 05:55
  • What prints when you do `console.log(item.createdAt)`? – Michael Rodriguez Dec 31 '19 at 06:05
  • When I try: createdAt: this.props.firebase.Timestamp.fromDate(new Date()), I get an error that says: TypeError: Cannot read property 'fromDate' of undefined – Mel Dec 31 '19 at 06:49
  • I was looking at how you use this.props.firebase and it seems like you have some custom methods that aren’t part of firebase admin sdk. Is that like a class that you created with helper methods in it? What passes in the firebase prop, and what is the value that is being passed in? Is it just your firebase instance, or is it a module that you created? – Michael Rodriguez Dec 31 '19 at 06:54
  • I have a helper in my firebase class - but I tried using it and it doesn't work either. The helper I made is: this.fieldValue = app.firestore.FieldValue; – Mel Dec 31 '19 at 06:55
  • I think we can solve this together. Right now I’m just trying to figure out why you can save to firebase using this.props.firebase, but you don’t have `Timestamp` (undefined) – Michael Rodriguez Dec 31 '19 at 06:55
  • I know I'm asking to talk to the database properly, because when I use the versions of the createdAt field that do actually save to the database (as shown above) - the input is working as expected - BUT I cannot then render the date back on the other side. – Mel Dec 31 '19 at 06:56
  • I don't understand any of this. People have been so kind in suggesting solutions - all of which seem to work for them but do not work for me. It's strange though that different solutions work for different users. – Mel Dec 31 '19 at 06:57
  • For example, why does this work to input a date: this.props.firebase.fieldValue.serverTimestamp()? -- note it does not allow me to render it back as output – Mel Dec 31 '19 at 06:58
  • How are you passing firebase in as a prop? – Michael Rodriguez Dec 31 '19 at 07:02
  • The form is inside a modal (AntDesign) so the state is visible/invisible – Mel Dec 31 '19 at 07:03
  • 1
    Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/205125/discussion-between-michael-rodriguez-and-mel). – Michael Rodriguez Dec 31 '19 at 07:08
  • Hi - this solution no longer works. Any ideas? https://stackoverflow.com/questions/61166977/rendering-a-firestore-timestamp-in-react – Mel Apr 12 '20 at 04:55
6

When you get timestamps from Firestore they are of the following type:

enter image description here

To convert this into a normal timestamp you can use the .toDate() function.

For example, for a document like the following:

enter image description here

We can use something like:

db.collection('[COLLECTION]').doc('[DOCUMENT]').get().then(function(doc) {
  console.log(doc.data().[FIELD].toDate());
});

and the output will be like:

2019-12-16T16:27:33.031Z

Now to process that timestamp further, you can convert it into a string and use regex to modify it according to your needs.

For example: (I'm using Node.js here)

db.collection('[COLLECTION]').doc('[DOCUMENT]').get().then(function(doc) {
  var stringified = doc.data().[FIELD].toDate().toISOString();
  //console.log(stringified);
  var split1 = stringified.split('T');
  var date = split1[0].replace(/\-/g, ' ');
  console.log(date);
  var time = split1[1].split('.');
  console.log(time[0]);
});

Will give you an output like this:

enter image description here

Waelmas
  • 1,894
  • 1
  • 9
  • 19
  • Thank you for the example. I have the same structure in my attempt. It works to return all the string values in the document but not the date. Instead, it generates an error with the message I posted above. It doesn't make any sense. I tried adding the toISOString() extension but it doesn't change the error message (that should just be formatting) – Mel Dec 11 '19 at 22:19
  • Can you log the value of the timestamp in console? Just to confirm it's the correct type of Firestore timestamp. @Mel – Waelmas Dec 12 '19 at 07:08
  • No - the console log doesn't work - it generates an error too - but the date gets recorded in the createdAt field in the table - now I'm just trying to read it back. I created a post explaining how I got the date to record (not per the firebase documentation) here: https://stackoverflow.com/questions/59257755/firebase-how-to-add-timestamp-to-database-in-firestore-where-react-context-ap – Mel Dec 12 '19 at 08:18
  • Since you are following a different approach than the one recommended by the official docs, I'm not sure what is really going wrong. It seems like the way you are creating and pushing the timestamp in the first place is somehow faulty. When you store a timestamp to Firestore it need to be an object of the format I have mentioned at the beginning of my answer. This way it gets recognized as valid timestamp that can then be used with the .toDate(). Firestore.Timestamp.now() will give you a timestamp of the correct format to check it out. @Mel – Waelmas Dec 12 '19 at 13:12
  • I tried following the official documentation to create a time stamp. I couldn't get it to work. I stumbled on an alternative way to record a date and now cannot read back the output! So frustrating. Thanks anyway for trying to help. – Mel Dec 12 '19 at 21:32
  • I managed to get a date into firebase using the approach shown in the Friendly Eats example project ( createdAt: new Date() )(not the Firebase documentation). But I still can't find a way to read it back. – Mel Dec 15 '19 at 03:50
  • Could you please update the question providing the code you used to store the timestamp, and how you tried to read it ? Also try to use the way I have mentioned to read it (Don't forget the .field_name.todate(); ) – Waelmas Dec 15 '19 at 12:34
  • Hi @Waelmas - I updated this post with both attempts. They make the same entry in the database but produce different errors in the output. Any ideas on what to try next would be super helpful. I'm out of ideas for what to try to do. It seems solutions that have worked for others don't work for some others with seemingly similar work patterns. – Mel Dec 15 '19 at 22:51
  • do you have any further ideas on what to try to be able to store and then also render a date? I posted a new attempt and the resulting errors here: https://stackoverflow.com/questions/59257755/firebase-how-to-add-timestamp-to-database-in-firestore-where-react-context-ap – Mel Dec 21 '19 at 05:33
2

So firestore stores dates as an object with seconds and nanoseconds. If you want the time the user was created then you would reference user.createdAt.nanoseconds. This returns a unix timestamp.

How do you want to display date into in your app? If you want to get a date object then you can pass the timestamp into a date constructor like so new Date(user.createdAt.nanoseconds). Personally I enjoy using the date-fns library for handling time.

Josh Pittman
  • 7,024
  • 7
  • 38
  • 66
  • Thanks - I tried this suggestion. It returns an error that says: TypeError: Cannot read property 'nanoseconds' of undefined. I'll have a look at the library you suggested now – Mel Dec 11 '19 at 02:11
  • Can you log createdAt and share please. Given the async nature of the data, you probably just need to return it like so `user.createdAt && new Date(user.createdAt.nanoseconds)`. Date-fns won't help with this problem, that just a handy library for displaying the date once you have it. – Josh Pittman Dec 11 '19 at 02:23
  • createdAt logs a long list of dropdown menus. I can't find one containing the date shown in the field in firestore. The first part is: createdAt: ServerTimestampFieldValueImpl _methodName: "FieldValue.serverTimestamp" __proto__: FieldValueImpl constructor: ƒ ServerTimestampFieldValueImpl() instance: ServerTimestampFieldValueImpl _methodName: "FieldValue.serverTimestamp" __proto__: FieldValueImpl constructor: ƒ ServerTimestampFieldValueImpl() instance: ServerTimestampFieldValueImpl {_methodName: "FieldValue.serverTimestamp"} arguments: (...) caller: (...) – Mel Dec 11 '19 at 02:32
  • It sounds like you have pushed the timestamp function to the database, not an actual timestamp. How are you setting the timestamp? `firestore.FieldValue.serverTimestamp()` – Josh Pittman Dec 11 '19 at 02:36
  • The entry showing in firestore is the photo I attached. This is the entry: this.props.firebase.fieldValue.serverTimestamp() – Mel Dec 11 '19 at 02:48
2

How to send the date to Firestore:

import firebase from 'firebase/app';

// ..........
// someObject is the object you're saving to your Firestore.

someObject.createdAt: firebase.firestore.Timestamp.fromDate(new Date())

How to read it back:

function mapMonth(monthIndex) {
  const months = {
    0: 'jan',
    1: 'feb',
    2: 'mar',
    3: 'apr',
    4: 'may',
    5: 'jun',
    6: 'jul',
    7: 'aug',
    8: 'sep',
    9: 'oct',
    10: 'nov',
    11: 'dec'
  };
  return months[monthIndex];
}

// ..........
// Here you already read the object from Firestore and send its properties as props to this component.

return(
    <LS.PostDate_DIV>
      Published on {mapMonth(props.createdAt.toDate().getMonth()) + ' '}
      of {props.createdAt.toDate().getFullYear()}
    </LS.PostDate_DIV>
  );
}

Basically when you do createdAt.toDate() you get a JS Date object.

I use it like this all the time!

From: https://cloud.google.com/firestore/docs/manage-data/add-data

enter image description here

This will create a data based on the user's system date. If you need to be sure that everything will be stored chronologically (without any errors of wrong system dates set by your users system) on your DB, you should use a server timestamp. The date will be set using your Firestore DB internal date system.

enter image description here

cbdeveloper
  • 27,898
  • 37
  • 155
  • 336
  • Thanks for this, but the trouble I'm trying to overcome is that I can't find a way to display the date recorded in the database. I keep getting errors of the kinds I have shared. – Mel Dec 17 '19 at 17:14
2

With a document ID of a User that does have the createdAt property set, try the following:

const docRef = db.collection("users").doc("[docID]");

docRef.get().then(function(docRef) {
  if (docRef.exists) {
     console.log("user created at:", docRef.data().createdAt.toDate());
  }
})

I'ts important to call the .data() method before accessing the document's properties

Note that if you access docRef.data().createdAt.toDate() of a user for which the createdAt propery is not set, you will get TypeError: Cannot read property 'toDate' of undefined

So in case you have any user in your collection that has no createdAt property defined. You should implement a logic to check if the user has the createdAt property before getting it. You can do something like this:

//This code gets all the users and logs it's creation date in the console
docRef.get().then(function(docRef) {
  if (docRef.exists && docRef.data().createdAt) {
      console.log("User created at:", docRef.data().createdAt.toDate());
  }
})
Jose V
  • 1,356
  • 1
  • 4
  • 12
  • As you can see above, I do use data() in my requests for document fields. If I didn't the other fields would not render. The specific issue is with the time stamp, which does not render. One of the attempts I made, and which I set out above, includes the attempt using the toDate() extension. It does not work - for the reasons set out above. Thanks anyway for your time. – Mel Dec 21 '19 at 04:07
0

I've encountered problems in the past with nested object properties not rendering properly in lists where item.someProp is considered an object, but item.someProp.someSubProp won't resolve with the value of someSubProp in item.someProp.

So to skirt around the issue, why not evaluate Timestamp to a plain date object (or the desired display format) when creating the user object?

this.unsubscribe = this.props.firebase
  .users()
  .onSnapshot(snapshot => {
    let users = [];

    snapshot.forEach(doc =>
      let docData = doc.data();
      docData.createdAt = docData.createdAt.toDate();
      users.push({ ...docData, uid: doc.id }),
    );

    this.setState({
      users,
      loading: false,
    });
  });
samthecodingman
  • 23,122
  • 4
  • 30
  • 54
  • Also worth noting that both [`DocumentSnapshot#data()`](https://firebase.google.com/docs/reference/js/firebase.firestore.DocumentSnapshot.html#data) and [`DocumentSnapshot#get()`](https://firebase.google.com/docs/reference/js/firebase.firestore.DocumentSnapshot.html#get) accept an object that specifies how to handle not-yet-finalized `FieldValue.serverTimestamp()` values. By default, a not-yet-finalized will simply be null. Using `{ serverTimestamps: "estimate"}` will change these null values to an estimate. – samthecodingman Dec 27 '19 at 03:32
  • Hi - thanks for the suggestion. I'd like to try it but I don't understand it - or how I can try to implement it. Is there a convention for the principles you're applying in coming up with this suggestion. If I can find literature about the approach, I'll try to figure out how your idea works so that I can try it in my code. – Mel Dec 31 '19 at 04:59
  • I haven't got any particular resource for it, as I picked it up after looking at [other StackOverflow questions](https://stackoverflow.com/search?q=nested+object+react) over the years. – samthecodingman Dec 31 '19 at 10:33
0

I solved this issue by first converting my timestamp value to a string, creating a separate state called date and passing it into my react component

const [date, setDate] = useState("")

setDate(item.createdAt.toDate().toString())

 <ListItem> {date} </ListItem>
Yetunde
  • 1
  • 1