React - afficher un horodatage Firestore

10

J'essaie de comprendre comment afficher un horodatage Firestore dans une application React.

J'ai un document Firestore avec un champ nommé createdAt.

J'essaie de l'inclure dans une liste de sortie (en extrayant les bits pertinents ici afin que vous n'ayez pas à lire toute la liste des champs).

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

Le seul attribut qui ne sera pas rendu est la date.

Chacune des tentatives ci-dessus génère une erreur qui dit:

TypeError: Impossible de lire la propriété 'toDate' de indéfini

J'ai vu ce post , et ce post et ce post , et ce post et d'autres comme eux, qui suggèrent que toDate () devrait fonctionner. Mais - cette extension génère une erreur pour moi - y compris lorsque j'essaye l'extension toString.

Je sais qu'il sait qu'il y a quelque chose dans firestore parce que lorsque j'essaye user.createdAt, j'obtiens une erreur disant qu'il a trouvé un objet avec des secondes dedans.

En prenant l'exemple de Waelmas ci-dessous, j'ai essayé de consigner la sortie du champ comme suit:

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

}

J'ai également essayé d'ajouter cela à ma déclaration de carte, mais j'obtiens une erreur indiquant que user.get n'est pas une fonction.

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

Il génère le même message d'erreur que ci-dessus.

entrez la description de l'image ici

PROCHAINE TENTATIVE

Une chose étrange qui est survenue en essayant de trouver un moyen d'enregistrer une date dans Firestore qui me permet de la relire ensuite, c'est que lorsque je change de gestionnaire de soumission sous une forme pour utiliser cette 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();
    };

Cela fonctionne pour enregistrer une date dans la base de données.

La forme de l'entrée Firestore ressemble à ceci:

entrez la description de l'image ici

J'essaie d'afficher une date dans ce composant:

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

Quand j'essaye de le relire - en utilisant:

{item.email}

Le message d'erreur se lit comme suit:

Erreur: les objets ne sont pas valides en tant qu'enfant React (trouvé: horodatage (secondes = 1576363035, nanosecondes = 52000000)). Si vous vouliez rendre une collection d'enfants, utilisez plutôt un tableau. dans l'élément (à UserIndex.jsx: 74)

Lorsque j'essaie d'utiliser chacune de ces tentatives:

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

Je reçois une erreur qui dit:

TypeError: Impossible de lire la propriété 'toDate' de indéfini

Sur la base de la possibilité de relire les entrées enregistrées dans le même document dans d'autres champs, je m'attends à ce que l'une d'entre elles fonctionne pour produire une sortie, même si elle n'est pas formatée comme je le souhaite. Cela n'arrive pas.

PROCHAINE TENTATIVE

En prenant l'exemple de Waelmas, j'ai essayé de suivre les instructions, mais là où nous n'obtenons pas la même réponse, c'est dans la première étape. Lorsque Walemas obtient une sortie basée sur l'extension .toDate (), j'obtiens une erreur disant que toDate () n'est pas une fonction.

Conformément à la documentation de Firebase, j'ai essayé:

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

}})

Cela produit une chaîne d'erreurs avec la syntaxe et je ne peux pas trouver un moyen de les contourner.

PROCHAINE TENTATIVE

J'ai ensuite essayé de créer un nouveau formulaire pour voir si je pouvais l'explorer sans l'aspect authentification du formulaire utilisateurs.

J'ai un formulaire qui prend en entrée:

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

Dans le formulaire précédent, la nouvelle tentative Date () a fonctionné pour enregistrer une date dans la base de données, dans cet exemple, les deux champs pour createdAt et createdAt1 génèrent la même entrée de base de données:

entrez la description de l'image ici

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

Lorsque j'essaie de sortir la valeur des dates, la première génère une erreur qui dit:

Les objets ne sont pas valides en tant qu'enfant React (trouvé: dim. 15 déc.2019 21:33:32 GMT + 1100 (Australian Eastern Daylight Time)). Si vous vouliez rendre une collection d'enfants, utilisez plutôt un tableau

Le second génère l'erreur en disant:

TypeError: Impossible de lire la propriété 'toDate' de indéfini

Je suis coincé pour des idées sur quoi essayer ensuite.

J'ai vu ce post qui suggère que ce qui suit pourrait faire quelque chose d'utile:

                {item.createdAt1.Date.valueOf()}

Ce n'est pas le cas. Il rend une erreur qui dit:

TypeError: Impossible de lire la propriété 'Date' de non défini

Ce message semble avoir le même problème que moi, mais n'entre pas dans la façon dont ils ont réussi à afficher la valeur de date qu'ils ont stockée.

Ce message semble être bloqué sur le message d'erreur du tableau, mais semble avoir trouvé comment afficher une date à l'aide de createdAt.toDate ()

Mel
la source

Réponses:

1

Après quelques discussions, nous avons constaté que les horodatages dans l'objet utilisateur OPs pouvaient être des rendus en tant que tels:

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")}

J'ai recréé votre exemple dans un projet React factice et j'ai reçu la même erreur que prévu.

Erreur: les objets ne sont pas valides en tant qu'enfant React

J'ai pu obtenir ce rendu correctement avec la méthode suivante, qui devrait également fonctionner pour vous:

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

Qui, pour mon exemple d'horodatage, rendu comme:

30/12/2019


Assurez-vous que vous utilisez un horodatage enregistré dans Firestore en tant que:

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

Remarque: cela suppose que votre instance de firebase.firestore() est à this.props.firebase. Dans d'autres exemples, vous utilisez this.props.firebase, mais ces méthodes ressemblent à des méthodes d'assistance que vous avez créées vous-même.

Lorsque cette valeur est récupérée, ce sera un objet avec deux propriétés - _secondset_nanoseconds .

Assurez-vous d'inclure le soulignement. Si vous l'utilisez, createdAt.secondscela ne fonctionnera pas, il doit l'être createdAt._seconds.


J'ai essayé d'autres choses:

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

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

new Date(user.createdAt._nanoseconds) rend la mauvaise date

Michael Rodriguez
la source
Salut - merci pour la suggestion. J'ai essayé ceci et j'obtiens une erreur qui dit: TypeError: Impossible de lire la propriété 'Horodatage' d'undefined
Mel
J'ai également essayé: createdAt: firebase.firestore.fieldValue.serverTimestamp (). FromDate (new Date ()), qui renvoie une erreur qui dit: TypeError: Impossible de lire la propriété 'fieldValue' de indéfini
Mel
Bien que cela n'ait pas fonctionné pour moi, je suis intéressé de comprendre comment vous avez pensé à essayer le format que vous avez utilisé. Ce n'est pas comme indiqué dans la documentation. Si je peux apprendre comment vous avez découvert quelque chose qui fonctionne pour vous, cela pourrait me permettre de trouver un moyen qui fonctionne pour moi (je ne comprends pas pourquoi ce n'est pas la même chose pour tout le monde - mais j'ai besoin de nouvelles idées de choses à essayer ). Merci encore d'avoir essayé d'aider.
Mel
Ce que je peux faire, c'est enregistrer une date (comme indiqué ci-dessus) en utilisant: createdAt: this.props.firebase.fieldValue.serverTimestamp (),
Mel
1
Continuons cette discussion dans le chat .
Michael Rodriguez
5

Lorsque vous obtenez des horodatages de Firestore, ils sont du type suivant:

entrez la description de l'image ici

Pour convertir cela en un horodatage normal, vous pouvez utiliser la fonction .toDate ().

Par exemple, pour un document comme le suivant:

entrez la description de l'image ici

Nous pouvons utiliser quelque chose comme:

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

et la sortie sera comme:

2019-12-16T16:27:33.031Z

Maintenant, pour traiter davantage cet horodatage, vous pouvez le convertir en chaîne et utiliser l'expression régulière pour le modifier selon vos besoins.

Par exemple: (j'utilise Node.js ici)

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]);
});

Vous donnera une sortie comme celle-ci:

entrez la description de l'image ici

Waelmas
la source
Merci pour l'exemple. J'ai la même structure dans ma tentative. Il fonctionne pour renvoyer toutes les valeurs de chaîne dans le document mais pas la date. Au lieu de cela, il génère une erreur avec le message que j'ai publié ci-dessus. Cela n'a aucun sens. J'ai essayé d'ajouter l'extension toISOString () mais cela ne change pas le message d'erreur (qui devrait simplement être formaté)
Mel
Pouvez-vous enregistrer la valeur de l'horodatage dans la console? Juste pour confirmer que c'est le bon type d'horodatage Firestore. @Mel
Waelmas
Non - le journal de la console ne fonctionne pas - il génère également une erreur - mais la date est enregistrée dans le champ createdAt du tableau - maintenant j'essaie juste de le relire. J'ai créé un article expliquant comment j'ai obtenu la date d'enregistrement (pas selon la documentation de Firebase) ici: stackoverflow.com/questions/59257755/…
Mel
Puisque vous suivez une approche différente de celle recommandée par les documents officiels, je ne suis pas sûr de ce qui se passe vraiment mal. Il semble que la façon dont vous créez et appuyez sur l'horodatage en premier lieu est en quelque sorte défectueuse. Lorsque vous stockez un horodatage dans Firestore, il doit s'agir d'un objet du format que j'ai mentionné au début de ma réponse. De cette façon, il est reconnu comme horodatage valide qui peut ensuite être utilisé avec le .toDate (). Firestore.Timestamp.now () vous donnera un horodatage du format correct pour le vérifier. @Mel
Waelmas
J'ai essayé de suivre la documentation officielle pour créer un horodatage. Je ne pouvais pas le faire fonctionner. Je suis tombé sur une autre façon d'enregistrer une date et maintenant je ne peux pas relire la sortie! Si frustrant. Merci quand même d'essayer d'aider.
Mel
2

Firestore stocke donc les dates comme un objet avec des secondes et des nanosecondes. Si vous voulez l'heure de création de l'utilisateur, vous feriez référence user.createdAt.nanoseconds. Cela renvoie un horodatage Unix.

Comment voulez-vous afficher la date dans votre application? Si vous voulez obtenir un objet date, vous pouvez passer l'horodatage dans un constructeur de date comme ça new Date(user.createdAt.nanoseconds). Personnellement, j'aime utiliser la bibliothèque date-fns pour gérer le temps.

Josh Pittman
la source
Merci - j'ai essayé cette suggestion. Il retourne une erreur qui dit: TypeError: Impossible de lire la propriété 'nanosecondes' non définie. Je vais jeter un œil à la bibliothèque que vous avez suggérée maintenant
Mel
Pouvez-vous vous connecter créé et partager s'il vous plaît. Compte tenu de la nature asynchrone des données, vous avez probablement juste besoin de les renvoyer ainsi user.createdAt && new Date(user.createdAt.nanoseconds). Date-fns ne résoudra pas ce problème, c'est juste une bibliothèque pratique pour afficher la date une fois que vous l'avez.
Josh Pittman
createdAt enregistre une longue liste de menus déroulants. Je ne trouve pas un contenant la date indiquée sur le terrain dans le magasin de pompiers. La première partie est: createdAt: ServerTimestampFieldValueImpl _methodName: "FieldValue.serverTimestamp" proto : constructeur FieldValueImpl: ƒ ServerTimestampFieldValueImpl () par exemple: ServerTimestampFieldValueImpl _methodName: "FieldValue.serverTimestamp" proto : constructeur FieldValueImpl: ƒ ServerTimestampFieldValueImpl () par exemple: ServerTimestampFieldValueImpl {_methodName: " FieldValue.serverTimestamp "} arguments: (...) appelant: (...)
Mel
Il semble que vous ayez poussé la fonction d'horodatage vers la base de données, pas un horodatage réel. Comment définissez-vous l'horodatage? firestore.FieldValue.serverTimestamp()
Josh Pittman
L'entrée montrant dans le magasin de pompiers est la photo que j'ai jointe. Ceci est l'entrée: this.props.firebase.fieldValue.serverTimestamp ()
Mel
2

Comment envoyer la date à Firestore:

import firebase from 'firebase/app';

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

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

Comment le relire:

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>
  );
}

Fondamentalement, lorsque vous createdAt.toDate()obtenez un objet Date JS.

Je l'utilise comme ça tout le temps!

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

entrez la description de l'image ici

Cela créera des données basées sur la date système de l'utilisateur. Si vous devez être sûr que tout sera stocké chronologiquement (sans aucune erreur de dates système incorrectes définies par le système de vos utilisateurs) sur votre base de données, vous devez utiliser un horodatage du serveur. La date sera définie à l'aide de votre système de date interne Firestore DB.

entrez la description de l'image ici

cbdeveloper
la source
Merci pour cela, mais le problème que j'essaie de surmonter est que je ne trouve pas un moyen d'afficher la date enregistrée dans la base de données. Je continue à recevoir des erreurs du type de celles que j'ai partagées.
Mel
2

Avec un ID de document d'un utilisateur qui a la createdAtpropriété définie, essayez ce qui suit:

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

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

Il est important d'appeler la .data()méthode avant d'accéder aux propriétés du document

Notez que si vous accédez à docRef.data().createdAt.toDate()un utilisateur pour lequel la createdAtpropriété n'est pas définie, vous obtiendrezTypeError: Cannot read property 'toDate' of undefined

Donc, si vous avez un utilisateur dans votre collection qui n'a pas de createdAtpropriété définie. Vous devez implémenter une logique pour vérifier si l'utilisateur possède la createdAtpropriété avant de l'obtenir. Vous pouvez faire quelque chose comme ça:

//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
la source
Comme vous pouvez le voir ci-dessus, j'utilise data () dans mes demandes de champs de document. Si je ne le faisais pas, les autres champs ne seraient pas rendus. Le problème spécifique concerne l'horodatage, qui ne s'affiche pas. L'une des tentatives que j'ai faites, et que j'ai exposée ci-dessus, comprend la tentative d'utilisation de l'extension toDate (). Cela ne fonctionne pas - pour les raisons exposées ci-dessus. Merci quand même pour votre temps.
Mel
0

J'ai rencontré des problèmes dans le passé avec des propriétés d'objet imbriquées ne s'affichant pas correctement dans des listes où item.somePropest considéré comme un objet, maisitem.someProp.someSubProp qui ne s'affichent pas ne se résolvent pas avec la valeur de someSubPropinitem.someProp .

Donc, pour contourner le problème, pourquoi ne pas évaluer l'horodatage en un objet de date ordinaire (ou le format d'affichage souhaité) lors de la création de l'objet utilisateur?

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
la source
Il convient également de noter que les deux DocumentSnapshot#data()et DocumentSnapshot#get()acceptent un objet qui spécifie comment gérer les FieldValue.serverTimestamp()valeurs non encore finalisées . Par défaut, un pas encore finalisé sera simplement nul. L'utilisation { serverTimestamps: "estimate"}changera ces valeurs nulles en estimation.
samthecodingman
Salut - merci pour la suggestion. Je voudrais l'essayer mais je ne le comprends pas - ni comment je peux essayer de le mettre en œuvre. Y a-t-il une convention pour les principes que vous appliquez pour formuler cette suggestion. Si je peux trouver de la documentation sur l'approche, j'essaierai de comprendre comment fonctionne votre idée afin de pouvoir l'essayer dans mon code.
Mel
Je n'ai pas de ressource particulière pour cela, car je l'ai récupéré après avoir examiné d' autres questions sur StackOverflow au fil des ans.
samthecodingman
Merci d'avoir partagé. Je vais les lire ce soir
Mel