質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Firebase

Firebaseは、Googleが提供するBasSサービスの一つ。リアルタイム通知可能、並びにアクセス制御ができるオブジェクトデータベース機能を備えます。さらに認証機能、アプリケーションのログ解析機能などの利用も可能です。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

TypeScript

TypeScriptは、マイクロソフトによって開発された フリーでオープンソースのプログラミング言語です。 TypeScriptは、JavaScriptの構文の拡張であるので、既存の JavaScriptのコードにわずかな修正を加えれば動作します。

React.js

Reactは、アプリケーションのインターフェースを構築するためのオープンソースJavaScriptライブラリです。

Q&A

1回答

3136閲覧

Firebase+reactのアプリで無限にレンダリングされる

kobaryo04ysh

総合スコア29

Firebase

Firebaseは、Googleが提供するBasSサービスの一つ。リアルタイム通知可能、並びにアクセス制御ができるオブジェクトデータベース機能を備えます。さらに認証機能、アプリケーションのログ解析機能などの利用も可能です。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

TypeScript

TypeScriptは、マイクロソフトによって開発された フリーでオープンソースのプログラミング言語です。 TypeScriptは、JavaScriptの構文の拡張であるので、既存の JavaScriptのコードにわずかな修正を加えれば動作します。

React.js

Reactは、アプリケーションのインターフェースを構築するためのオープンソースJavaScriptライブラリです。

0グッド

0クリップ

投稿2020/12/17 11:25

編集2020/12/18 08:56

実現したいこと

最終的に実現したいことは、コメントの一覧に投稿した時刻を表示することです。

背景

コメントフォーム

上記の画像の様に、formと表示が一体となっているコメント一覧をReactTypeScriptFirestoreを用いて作成しています。

コメントを投稿すると、firestoreのonSnapshotでリアルタイムで検知してしたのコメント一覧に表示するという仕組みになっていて、実装自体は完了していて、リアルタイムでの表示もうまくできています。

ソースコード

以下は該当部分のソースコードです。
※importなど少し省略しています。
CommentForm.tsx

CommentForm.tsx

1export default function CommentForm({ id }: any) { 2 const [loading, setLoading] = useState(false); 3 const [error, setError] = useState(""); 4 const [content, setContent] = useState(""); 5 const { currentUser, createComment }: any = useAuth(); 6 const [open, setOpen] = React.useState(false); 7 const [comment, setComment] = useState([]); 8 9 const handleClose = (event?: React.SyntheticEvent, reason?: string) => { 10 if (reason === "clickaway") { 11 return; 12 } 13 setOpen(false); 14 }; 15 16 const inputContent = useCallback( 17 (event) => { 18 setContent(event.target.value); 19 }, 20 [setContent] 21 ); 22 23 const handleSubmit = async (e: any) => { 24 e.preventDefault(); 25 26 if (content === "") { 27 return setError("コメントを入力してください"), setOpen(true); 28 } 29 if (content.length > 200) { 30 return setError("200文字以内で入力してください"), setOpen(true); 31 } 32 33 try { 34 setError(""); 35 setLoading(true); 36 const uid = currentUser.uid; 37 return createComment(id, content, uid); 38 } catch { 39 setError("投稿に失敗しました"); 40 setOpen(true); 41 } finally { 42 setLoading(false); 43 setContent(""); 44 } 45 }; 46 47 useEffect(() => { 48 let comments: any = []; 49 const unsubscribe: any = db 50 .collection("posts") 51 .doc(id) 52 .collection("comments") 53 .orderBy("createdAt", "desc") 54 .onSnapshot((snapshots) => { 55 snapshots.docChanges().forEach((change) => { 56 const data = change.doc.data({ serverTimestamps: "estimate" }); 57 const changeType = change.type; 58 const date = data.createdAt.toDate(); 59 60 switch (changeType) { 61 case "added": 62 comments.push({ ...data, createdAt: date }); 63 break; 64 case "modified": 65 const index = comments.findIndex( 66 (comment: any) => comment.id === change.doc.id 67 ); 68 comments[index] = comment; 69 break; 70 case "removed": 71 comments = comments.filter( 72 (comment: any) => comment.id !== change.doc.id 73 ); 74 break; 75 default: 76 break; 77 } 78 }); 79 setComment(comments); 80 return () => unsubscribe(); 81 }); 82 }, []); 83 84 return ( 85 <> 86 <form noValidate autoComplete="off" onSubmit={handleSubmit}> 87 <textarea 88 className="comment-textarea" 89 name="content" 90 id="content" 91 placeholder="コメントを残す" 92 onChange={inputContent} 93 value={content} 94 ></textarea> 95 <div className="comment-submit-wrapper"> 96 <Button 97 className="comment-submit" 98 color="primary" 99 variant="contained" 100 type="submit" 101 disabled={loading} 102 > 103 投稿する 104 </Button> 105 </div> 106 </form> 107 {console.log(comment)} 108 {comment.map((commentItem: any) => ( 109 <Comment 110 key={commentItem.id} 111 id={commentItem.id} 112 uid={commentItem.uid} 113 content={commentItem.content} 114 createdAt={commentItem.createdAt} 115 /> 116 ))} 117 </> 118 ); 119}

CommentForm.tsxhandleSubmit 内にあるcreateComment の中身は以下の通りです。

AuthContext.jsx

1// timestampはfirebaseのserverTimestampです。 2 const createComment = (postId: string, content: string, uid: string) => { 3 db.collection("posts") 4 .doc(postId) 5 .collection("comments") 6 .add({ 7 content: content, 8 createdAt: timestamp, 9 uid: uid, 10 }) 11 .then(async (result: any) => { 12 const id = result.id; 13 const commentsRef = db 14 .collection("posts") 15 .doc(postId) 16 .collection("comments") 17 .doc(id); 18 await commentsRef.set({ id: id }, { merge: true }); 19 }); 20 };

Comment.tsx

Comment.tsx

1export default function Comment({ id, uid, content, createdAt }: any) { 2 const [commentUsername, setCommentUsername] = useState(); 3 4 useEffect(() => { 5 db.collection("users") 6 .doc(uid) 7 .get() 8 .then((snapshot: any) => { 9 const data: any = snapshot.data(); 10 const username = data.username; 11 setCommentUsername(username); 12 }); 13 }, []); 14 15 return ( 16 <div className="comment-block"> 17 <p>{commentUsername}</p> 18 <p>{content}</p> 19 {/* <p>{createdAt}</p> */} 20 {/* createdAtを入れるとError: Objects are not valid as a React childというエラーになる */} 21 </div> 22 ); 23}

問題点

実装自体はうまくいっているのですが、リアルタイムで投稿を表示するために、CommentForm.tsxのuseEffectで投稿を取得する部分をgetからonSnapshotに変えたあたりから、何かしらコードを間違えたのか、console.log(comment)でuseEffectで取得した内容を入れたstateをlogに出力してみたところ、3つしかコメントがないのに、以下の画像の様にコンソールに大量にログが出力されました。

コンソール

もちろん、returnの中でsetStateの様なことをすると無限にレンダリングされることは知っているのですが、今回はhooksのセッター関数はreturn内では使っていません。

また、propsとして、子のコンポーネントであるComment.tsxに渡したcreatedAtComment.tsxでlogに出力してももちろん変わらず無限にレンダリングされます。

さらに、Comment.tsxcreatedAtのpropsを無理やり表示しようとしてもError: Objects are not valid as a React child (found: Thu Dec 17 2020 19:48:18 GMT+0900 (日本標準時)). If you meant to render a collection of children, use an array instead.というエラーが出ます。

Reactについての理解が乏しく、原因がわかりそうになかったので質問させていただきました。

ご回答お待ちしております。

追記

useEffect内にてリスナーのデタッチのためのunsbscribeをreturnしていますが、ご指摘いただいた通り、returnを消してunsubscribe()だけに変更したらレンダリングが抑えられる様になりました。

しかしながら、時間が経って再びconsoleに出力すると、直す前と変わらず無限にレンダリングがされてしまっていました。その上で、リアルタイムのコメントの反映もされなくなってしまったので、onSnapshot((snapshots) => {})の外にreturn unsubscribe;とすることで、リアルタイムのコメントの反映の部分は直ったのですが、依然として無限にレンダリングされる現象が直りません。

CommentForm.tsx

1 useEffect(() => { 2 let comments: any = []; 3 const unsubscribe: any = db 4 .collection("posts") 5 .doc(id) 6 .collection("comments") 7 .orderBy("createdAt", "desc") 8 .onSnapshot((snapshots) => { 9 snapshots.docChanges().forEach((change) => { 10 const data = change.doc.data({ serverTimestamps: "estimate" }); 11 const changeType = change.type; 12 13 switch (changeType) { 14 case "added": 15 comments.push(data); 16 break; 17 case "modified": 18 const index = comments.findIndex( 19 (comment: any) => comment.id === change.doc.id 20 ); 21 comments[index] = comment; 22 break; 23 case "removed": 24 comments = comments.filter( 25 (comment: any) => comment.id !== change.doc.id 26 ); 27 break; 28 default: 29 break; 30 } 31 }); 32 setComment(comments); 33 }); 34 return unsubscribe; //変更箇所 35 }, []); 36

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

Firebase は経験ないのですが、ヤバイところはわかりました。useEffect の最後で再帰関数になっています。

JavaScript

1 return () => unsubscribe();

とくにこちらから何も返さなくても unsubscribe 関数は返ってきますのでここを削除すればよいでしょう。

投稿2020/12/17 16:42

A_kirisaki

総合スコア2853

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

kobaryo04ysh

2020/12/18 02:02

ご回答ありがとうございます!削除というのは、returnを削除して、unsbscribe()とすれば良いということですよね? それで試してみたところ、無限にレンダリングされるのは直りました!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問