-3

I am trying to add a time stamp to my firebase form data in my react app.

The app uses react context api to wrap the app in a FirebaseContext.Provider as follows:

ReactDOM.render(

  <FirebaseContext.Provider value={new Firebase()}>
    <App />

  </FirebaseContext.Provider>,
  document.getElementById('root'),
);

The context provider has:

import React from 'react';
const FirebaseContext = React.createContext(null);
export const withFirebase = Component => props => (
  <FirebaseContext.Consumer>
    {firebase => <Component {...props} firebase={firebase} />}
  </FirebaseContext.Consumer>
);
export default FirebaseContext;

Then, each component that needs firebase is wrapped in the consumer, as follows:

import Firebase, { withFirebase } from "../../firebase"

export default withFirebase(Register)

In my submit handler, I'm trying to add a timestamp to the form data:

handleCreate = () => {

    const { form } = this.formRef.props;
    form.validateFields((err, values) => {
      if (err) {
        return;
      }
      // console.log("does it know what firebase is?", this.props.firebase.db);
    //   console.log("try time", this.props.firebase.db.FieldValue.serverTimestamp());
      console.log("it can get this far");
      console.log(this.props.firebase.firestore.FieldValue.serverTimestamp());
      console.log("it can get this far 2");

      const payload = {
        ...values,
        // role: formState.role.value,
        // created: this.props.firebase.fieldValue.serverTimestamp(),
        //  console.log("try to get a timestamp to work", this.props.firebase.serverValue.TIMESTAMP),
        // console.log("it can get to payload"),

        // createdAt: this.props.firebase.serverValue.TIMESTAMP
        // createdAt: this.props.firebase.FieldValue.serverTimestamp

        // createdAt: this.props.firebase.firestore.FieldValue.serverTimestamp(),
      }
      // console.log("formvalues", payload);


      // console.log('Received values of form: ', values);
      this.props.firebase.db
          .collection("preregistrations")
          .add(payload)
          // .set(FieldValue.serverTimestamp())

          // .add (createdAt: this.props.firebase.firestore.FieldValue.serverTimestamp());

          // .add(
          //   createdAt: this.props.firebase.firestore.FieldValue.serverTimestamp()
          // )

          // .then(docRef => {
            // resetForm(initialValues);

          // })
          .then(e => this.setState({ modalShow: true }))
          .catch(error => {
            console.error("Error adding document: ", error);
          });

      form.resetFields();
      this.setState({ visible: false });
    });

};

My firebase config is setup as follows.

import app from 'firebase/app';
import 'firebase/auth';
import 'firebase/firestore';
import firestore from "firebase/firestore";


class Firebase {
  constructor() {
    app.initializeApp(config).firestore();
    this.auth = app.auth();
    // this.db = app.firebase.database()
    this.db = app.firestore();

  }  

Previously, this has worked:

createdAt: this.props.firebase.firestore.FieldValue.serverTimestamp()

The firebase documents say that FieldValue works with .set and .update. Where those of the attempts I'm making include the "FieldValue" step, I'm trying to use it with .add

The timestamp documentation uses this format to give a timestamp:

firebase. firestore.Timestamp

I've tried following this format as a line item in the payload as well as a step in sending data to the database:

createdAt: this.props.firebase.firestore.Timestamp,

Another approach that has previously worked is set out in this post. The answer and the firebase documents suggest that the timestamps only work with .set and .update, whereas I'm trying to use it with .add. Perhaps something has changed in firebase so the it no longer works with .add - but I tried adding a .set step after my .add step and that didn't work either.

this.props.firebase.db
          .collection("preregistrations")
          .add(payload)
          .set(FieldValue.serverTimestamp())

I have a tutorial that recommends using this approach.

console.log("try timestamp",  this.props.firebase.serverValue.TIMESTAMP),

It doesn't work. No error message gets generated and the console log does not run -the form just hangs and does not submit.

This tutorial comes with a book that directs to this tutorial for moving from realtime database to firestore. The only remark it makes about using timestamps is to say:

The set up for using timestamps, in this case for the createdData property for our message entities, has also changed slightly.

It does not disclose what the slight change is - but it does give 2 clues.

First - there is a repo linked to the post which has a timestamp in it like so:

  createdAt: this.props.firebase.fieldValue.serverTimestamp().

This is the same as the attempt previously made in the form submit handler.

Second - the post references this bit of the firestore documentation for how to figure out changing from realtime database to firestore. That bit of the documentation doesn't have anything to say about making a timestamp in firestore. The documentation does have an example application for FriendlyEats which has code with a table with a timestamp. It has:

 rating.timestamp = new Date();

It also has references to something removing timestamps from snapshots but I can't find the code before the example was updated to remove them so that I can find out how to add them.

This post suggests that it is necessary to import * as firebase to get the timestamps to work. If I import that, then I get a warning saying not to use the * except in development. Does this mean there is a separate package other than the ones I have imported in my firebase config (as below) that is needed to make timestamps work - and if there is - does it have a specific name - or is the * import necessary to get timestamps to work?

import app from 'firebase/app';
import 'firebase/auth';
import 'firebase/firestore';
import firestore from "firebase/firestore";

I tried following the approach in that post and:

  1. imported * in the form file:

    import * as firebase from 'firebase';

  2. added the following to the payload:

    createdAt: this.props.firebase.db.Timestamp,

It doesn't work - no error messages - but the form hangs when it gets to that step.

NEXT ATTEMPT

NEXT, I tried to use:

createdAt: this.props.firebase.fieldValue.serverTimestamp(),

This is because this tutorial (which loosely refers to changes requires to use dates with firestore includes a link to a repo that uses this format).

Then in firebase.js I have a helper as:

class Firebase {
  constructor() {
    app.initializeApp(config).firestore();


    /* Firebase APIs */
    this.auth = app.auth();
    this.db = app.firestore();

    /* helpers */
    this.fieldValue = app.firestore.FieldValue;

Note that in the helper, FieldValue is upper case for Field.

When I try this, I get an error that says:

FirebaseError: Function DocumentReference.set() called with invalid data. Unsupported field value: undefined (found in field createdAt)

When I try making the createdAt use similar casing in the timestamp creator (which the answer to this post suggests is correct), I try:

createdAt: this.props.firebase.FieldValue.serverTimestamp(),

This also fails. No error message but the form does not submit.

NEXT ATTEMPT

When I move the createdAt step out of the payload and just try to use in the submit handler step when the user is created in the database, like so:

TypeError: Cannot read property 'serverTimestamp' of undefined

return this.props.firebase.user(authUser.user.uid).set(
            {
              name: values.name,
              email: values.email,
              // createdAt: values.createdAt,
              createdAt: this.props.firebase.FieldValue.serverTimestamp(),

            },
            { merge: true }
          );
        })

the user set step is ignored by firebase (it does not create the record). The console error says:

TypeError: Cannot read property 'serverTimestamp' of undefined
Mel
  • 2,481
  • 26
  • 113
  • 273

6 Answers6

3

So - it turns out the Friendly Eats example repo works to record a date - but this still isn't an answer because it does not allow that date to be displayed.

When I drop all the tutorials and firestore documentation advice for how to make a timestamp, this line works to create this database entry:

enter image description here

      createdAt: new Date()

This also works if you want to get it to an ISOstring:

      createdAt: new Date().ISOstring

Next step is to figure out how to read it back.

Here is a helpful tutorial if anyone else is stuck figuring out how to record a timestamp.

Mel
  • 2,481
  • 26
  • 113
  • 273
1

To hopefully help you with server timestamp I have modified the simple react-firebase-hook app created as an answer for this question: https://stackoverflow.com/a/60504303/610053

  1. I've updated my Firebase class with an extra method called serverTimestamp:
class Firebase {
    constructor() {
        app.initializeApp(firebaseConfig);

        this.realtimedb = app.database();
        this.firestore = app.firestore();

        this.serverTimestamp = app.firestore.FieldValue.serverTimestamp;
    }
}

const FirebaseContext = React.createContext(null);

const App = () => {
    const firebase = React.useContext(FirebaseContext);
    const [counter, setCounter] = React.useState(-1);
    const [timestamp, setTimestamp] = React.useState(null);

    React.useEffect(() => {
        const unsubscribe = firebase.firestore.collection("counters").doc("simple").onSnapshot(documentSnapshot => {
            if(documentSnapshot) {
                const data = documentSnapshot.data();
                console.log("New data", data);
                setCounter(data.value);

                if(data.updatedAt) {
                    let initDate = new Date(0);
                    initDate.setSeconds(data.updatedAt.seconds);
                    setTimestamp(initDate);
                }
            }
        })

        return () => unsubscribe;
    }, []);

    const handleIncrement = () => {
        firebase.firestore.collection("counters").doc("simple").update({
            value: counter + 1,
            updatedAt: firebase.serverTimestamp(),
        });
    }

    return (<div>
        <div>
            Current counter value: {counter}. Updated: { timestamp ? timestamp.toString() : "Never?.." }
        </div>
        <div>
            <button onClick={handleIncrement}>Increment</button>
        </div>
    </div>);
};
fxdxpz
  • 1,969
  • 17
  • 29
1

using firestore is important:

import * as firebase from 'firebase';

createdAt: firebase.firestore.FieldValue.serverTimestamp()
Harsh Verma
  • 529
  • 6
  • 10
0

Yes, the * import necessary to get timestamps to work. You need to import the entire firebase package for timestamps (as well as that new array increment feature I believe).

I'm fairly sure the problem here is that you are confusing the actual firebase import with the class you have named Firebase in your config file.

In the file that you reference createdAt:this.props.firebase.fieldValue.serverTimestamp(). import import * as _firebase from 'firebase'; and then change it to createdAt:_firebase.fieldValue.serverTimestamp().

I'm using _ so that you don't get a naming collision.

If you still get an error when you try this please share in a comment below.

Josh Pittman
  • 7,024
  • 7
  • 38
  • 66
  • On a separate note, I think passing firebase down through context is a completely pointless exercise since you can just import it from the config file to anywhere you need it in your app. Using context is a completely unnecessary added layer of complexity. Is there a specific reason you are using context for this redundant use case? – Josh Pittman Dec 10 '19 at 04:17
  • Thanks. I'm trying this again now. Is everyone just ignoring the error about not importing the * outside the development environment? The tutorial suggests that the context wrapper is a feature improvement. it gives the reason that the whole app runs through and then components that need access to it can be wrapped. Then, the idea is that I can specify conditions with roles and permissions that feed into it. Is there a convention that is a better way of doing this? – Mel Dec 10 '19 at 04:26
  • I tried this. It doesn't work. It doesn't generate any error messages - the form just hangs. I tried console.log(_firebase.fieldValue.serverTimestamp()); after I define payload. with logs before and after. The one before runs and then it stops. – Mel Dec 10 '19 at 04:31
  • Looks like you figured it out. Nice one. About the previous point, my suggestion was to just import the files without context. I'm not sure if it is better, it is simpler. If you need to pass down 'specify conditions with roles and permissions' then ok, but if you don't then simpler is definitely better, especially when you are trying to learn. – Josh Pittman Dec 10 '19 at 04:51
0

You can use TIMESTAMP provided by Firebase

import * as firebase from 'firebase';

createdAt:firebase.database.ServerValue.TIMESTAMP

It will do the work for you

Guru Prasad mohapatra
  • 1,809
  • 13
  • 19
-1

This works (without importing the * as firebase from firebase)

createdAt: this.props.firebase.fieldValue.serverTimestamp()

I don't understand why this works - It's not the same format as what's shown in the firebase documents and it doesn't look like anything i've used before - but I'm not going to ask questions at this point. I hope this helps someone else.

Mel
  • 2,481
  • 26
  • 113
  • 273