javascriptreactjsfirebasegoogle-cloud-firestoreunix-timestamp

How can I display a Firestore timestamp in React?


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()


Solution

  • After some discussion, we found that the time stamps in OPs user object could be rendered 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