ServerTimeStampをyyyy/mm/dd hh:mmの形に変換し時刻を表示する
はじめに
前回の記事で扱ったチャットアプリに投稿時刻を追加します。
手順は以下の通りです。
- メッセージ送信時に serverTimeStamp で Firebase のサーバー時刻を取得する
- 変数 Messages に Firestore からデータを取得する
- オブジェクト messages に toDate() メソッドを用いて serverTimeStamp を変換し、プロパティを追加する。
- 画面上にレンダリングする
前提:追加前のコード
前回の記事の「変更後のコード」トピックスに記載しています。
投稿時刻表示の処理
メッセージ送信時に serverTimeStamp で Firebase のサーバー時刻を取得する
メッセージフォームの送信時に
firebase.firestore.FieldValue.serverTimestamp()
を用いてサーバー時刻を messages コレクションに追加します。
new Date()
ではユーザーのデバイスの日時が取得されるため、タイムゾーンが異なる場合に不都合が生じることから、Firebase のサーバー時刻を投稿時刻として取得しています。
export const Room = () => {
const [value, setValue] = useState("");
const timestamp = firebase.firestore.FieldValue.serverTimestamp();
const handleSubmit = (e) => {
e.preventDefault();
firebase.firestore().collection("messages").add({
content: value,
user: user.displayName,
time: timestamp,
avatar: user.photoURL,
});
setValue("");
};
...
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={value}
onChange={(e) => setValue(e.target.value)}
/>
<button type="submit">送信</button>
</form>
);
};
これで firestore の messages コレクションに time として投稿時刻が追加されました。
変数 Messages に Firestore からデータを取得する
次は Firestore の messages コレクションからonSnapshot()
メソッドでリアルタイムにデータを取得し、変数 messages を定義します。
useEffect(() => {
firebase
.firestore()
.collection("messages")
.orderBy("time")
.onSnapshot((snapshot) => {
let messages = snapshot.docs.map((doc) => {
return doc.data();
});
setMessages(messages);
}, []);
しかし、この time をコンソールに表示させると、
time: t {seconds: 1617202196, nanoseconds: 51000000}
という形になっており、このままでは表示できないため JS Date 型に変換する必要があります。
オブジェクト messages に toDate() メソッドを用いて serverTimeStamp を変換し、プロパティを追加する。
onSnapShot()
内に変換処理を追加します。
map 関数を用いてtoDate()
メソッドで serverTimeStamp を year,month,date,hour,min としてそれぞれ変換します。
変換したものはプロパティとしてオブジェクトに追加します。
その際、月日時分を2桁表示にしたいため、頭に 0 をつけて2桁にするよう処理を行っています。
messages = messages.map((message) => {
message.year = message.time.toDate().getFullYear();
message.month = ("0" + (message.time.toDate().getMonth() + 1)).slice(-2);
message.date = ("0" + message.time.toDate().getDate()).slice(-2);
message.hour = ("0" + message.time.toDate().getHours()).slice(-2);
message.min = ("0" + message.time.toDate().getMinutes()).slice(-2);
return message;
});
この状態で messages をコンソール上に表示すると、
となり、変換された数値が格納されています。
しかし、このままだと送信時に、
TypeError: Cannot read property 'toDate' of null
というエラーが出てしまいます。
これはメッセージ送信時に serverTimeStamp の作成をしますが、その作成途中で firestore のデータの取得が起こっているため、timestamp が null となっており、toDate()が使えなくなっています。
ですので、timestamp が null でも変換ができるように処理を追加します。
DocumentSnapshot の data メソッドには SnapShotOptions というオプションを指定することができます。serverTimestamps: "estimate" | "previous" | "none"
とあるように、3種類の値を指定できます。
値 | 説明 |
---|---|
estimate | 保留中(serverTimeStamp が null)の時は見積もり時刻を返してくれる。 timestamp を利用できるようになると、その値に変更する |
previous | 保留中の timestamp は無視し、更新前の値を返す |
省略 または none | timestamp を利用できるようになるまでは null で返す |
今回はserverTimestamps: "estimate"
を指定します。
useEffect(() => {
firebase
.firestore()
.collection("messages")
.orderBy("time")
.onSnapshot((snapshot) => {
let messages = snapshot.docs.map((doc) => {
return doc.data({ serverTimestamps: "estimate" });
});
messages = messages.map((message) => {
message.year = message.time.toDate().getFullYear();
message.month = ("0" + (message.time.toDate().getMonth() + 1)).slice(-2);
message.date = ("0" + message.time.toDate().getDate()).slice(-2);
message.hour = ("0" + message.time.toDate().getHours()).slice(-2);
message.min = ("0" + message.time.toDate().getMinutes()).slice(-2);
return message;
});
setMessages(messages);
});
}, []);
画面上にレンダリングする
Item コンポーネントに props を渡し、
{year}/{month}/{date} {hour}:{min}
の形でレンダリングします。
export const Room = () => {
...
return (
<>
<h1>Room</h1>
<ul>
{messages &&
messages.map((message, index) => {
return (
<Item
key={index}
user={message.user}
content={message.content}
avatar={message.avatar}
year={message.year}
month={message.month}
date={message.date}
hour={message.hour}
min={message.min}
/>
);
})}
</ul>
...
</>
);
};
export const Item = ({user, content, avatar, year, month, date, min, hour}) => {
return (
<li>
<img style={{ width: "100px" }} src={avatar} alt="" />
<br />
{user} : {content}
<br />
{year}/{month}/{date} {hour}:{min}
</li>
);
};
結果
最終的にこのように表示ができるようになりました。
送信時にもエラーは起きません。
Discussion