我正在尝试弄清楚如何创建清理功能,因为我不断收到错误消息

I am trying to figure out how to create a clean up function as I keep getting an error

我正在尝试弄清楚如何创建清理函数,因为我一直收到错误,如果我从 useEffect 依赖项中删除 "comments",错误就会消失,但应用程序不会实时更新,这是一个问题。如果有人使用过 React 和实时数据库甚至 Firestore,并且对我应该做什么有任何想法,请告诉我。

import React, { useContext, useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import { toast } from 'react-toastify';

import User from '../assets/images/user.svg';

import { AuthContext } from '../helpers/firebaseAuth';
import firebase from '../helpers/Firebase';
import Loading from '../helpers/Loading';

export const Comments = ({ match, history }) => {
    const { register, handleSubmit, reset } = useForm();

    const slug = match.params.slug;

    const {...currentUser} = useContext(AuthContext);

    const [comments, setComments] = useState([]);
    const [loading, setLoading] = useState(true);

    useEffect(() => {

        const fetchData = () => {
            const data = firebase.database().ref(`/posts/${slug}/comments`)

            data.once('value')
            .then((snapshot) => {
                if (snapshot) {
                    let comments = [];
                    const snapshotVal = snapshot.val();
                    for (let comment in snapshotVal) {
                        comments.push(snapshotVal[comment]);
                    }
                    setComments(comments);
                    setLoading(false);
                }
            });
        }
        fetchData();
    }, [slug, comments])


    if (loading) {
        return <Loading />;
    };

    const postComment = (values) => {

        console.log(!!currentUser.currentUser)

        if (!!currentUser.currentUser) {
            const comment = {
                commentText: values.commentText,
                commentCreator: currentUser.currentUser.displayName,
                currentUserId: currentUser.currentUser.uid,
            }

            const postRef = firebase.database().ref(`posts/${slug}/comments`);
            postRef.push(comment);

            reset();
        } else {
            toast.error('You are not authenticated ');
        }
    };

    const deleteComment = () => {

        console.log(comments[0].commentUserId);
        console.log(currentUser.currentUser.uid);

        if (currentUser.currentUser.uid === comments[0].commentUserId) {
            console.log('correct');
        }

        const key = firebase.database().ref(`posts/${slug}/comments`).once('value');

        key.then(snapshot => {
            console.log(snapshot.val());
        }).catch((error) => {
            console.log(error);
        });

    };

    const back = () => {
        history.push('./');
    };

    return (
        <div className='main' style={{ maxWidth: '600px' }}>
            <div className='see-user-comments' onClick={back} style={{ cursor: 'pointer', height: '50px' }}>
                Commenting on the post: {slug}
            </div>
            <div className='see-user-comments' style={{ padding: '10px 0' }}>
                <div>
                    <img src={User} alt='Profile' style={{ width: '30px' }} />
                    <span className='usertag-span'>{currentUser.displayName}</span>
                </div>
                <div>
                    <form onSubmit={handleSubmit(postComment)}>
                        <textarea 
                            name='commentText'
                            rows='3'
                            style={{ margin: '10px 0' }}
                            placeholder='Add to the conversation!'
                            ref={register} 
                        /> 
                        <span style={{ width: '90%' }}>
                            <button>Comment</button>
                        </span>
                    </form>
                </div>
            </div>
            {comments.map((comment, index) =>
            <div key={index} className='see-user-comments' style={{ padding: '15px 0' }}>
                <div style={{ height: '30px' }}>
                    <img src={User} alt='Profile' style={{ width: '30px' }} />
                    <div style={{ flexDirection: 'column', alignItems: 'flex-start', justifyItems: 'center' }}>
                        <span className='usertag-span'>{comment.commentCreator}</span>
                    </div>
                </div>
                <span className='commentText-span'>{comment.commentText}
                    { !!currentUser?.currentUser?.uid === comments[0].commentUserId ?
                        (<button onClick={deleteComment}>Delete</button>) : null 
                    }
                </span>
            </div>
            )}
        </div>
    )
}

export default Comments;

没有看到有问题的错误,我只能假设这是因为使用以下模式会导致无限循环,因为每次 count 更改时都会重新触发效果:

const [count, setCount] = useState(0);
useEffect(() => setCount(count + 1), [count]);

当您将 comments 添加到您的效果时,您在做同样的事情。

要解决此问题,您必须更改效果以依赖 Firebase 的实时事件来更新您的评论数组。这可以像将 once('value').then((snap) => {...}) 更改为 on('value', (snap) => {...}); 一样简单。因为这现在是一个实时侦听器,所以您还必须 return 一个从 useEffect 调用中取消订阅侦听器的函数。正确执行此操作的最少代码量是:

const [postId, setPostId] = useState('post001');

useEffect(() => {
    const postRef = firebase.database().ref('posts').child(postId);
    const listener = postRef.on(
        'value',
        postSnapshot => {
            const postData = postSnapshot.val();
            // ... update UI ...
        },
        err => {
            console.log('Failed to get post', err);
            // ... update UI ...
        }
    )

    return () => postRef.off('value', listener);

}, [postId]);

将这些更改应用于您的代码(以及一些 QoL 改进)会产生:

const { register, handleSubmit, reset } = useForm();

const slug = match.params.slug;

const { ...authContext } = useContext(AuthContext); // renamed: currentUser -> authContext (misleading & ambiguous)

const [comments, setComments] = useState([]);
const [loading, setLoading] = useState(true);

let _postCommentHandler, _deleteCommentHandler;

useEffect(() => {
    // don't call this data - it's not the data but a reference to it - always call it `somethingRef` instead
    const postCommentsRef = firebase.database().ref(`/posts/${slug}/comments`);

    // create realtime listener
    const listener = postCommentsRef.on(
        'value',
        querySnapshot => {
            let _comments = [];
            querySnapshot.forEach(commentSnapshot => {
                const thisComment = commentSnapshot.val();
                thisComment.key = commentSnapshot.key; // store the key for delete/edit operations
                _comments.push(thisComment);
            });
            setComments(_comments);
            setLoading(false);
        },
        err => {
            console.log(`Error whilst getting comments for post #${slug}`, err);
            // TODO: handle error
        });

    // update new comment handler
    _postCommentHandler = (formData) => {
        console.log({
            isLoggedIn: !!authContext.currentUser
        });

        if (!authContext.currentUser) {
            toast.error('You are not authenticated ');
            return;
        }

        const newComment = {
            commentText: formData.commentText, // suggested: commentText -> content
            commentCreator: authContext.currentUser.displayName, // suggested: commentCreator -> author
            currentUserId: authContext.currentUser.uid, // suggested: commentUserId -> authorId
        }

        postCommentsRef.push(newComment)
            .then(() => {
                // commented successfully
                reset(); // reset form completely
            })
            .catch(err => {
                console.log(`Error whilst posting new comment`, err);
                // TODO: handle error
                reset({ commentText: formData.commentText }) // reset form, but leave comment as-is
            })
    }

    // update delete handler
    _deleteCommentHandler = () => {
        if (!comments || !comments[0]) {
            console.log('Nothing to delete');
            return;
        }

        const commentToDelete = comments[0];

        console.log({
            commentUserId: commentToDelete.commentUserId,
            currentUser: authContext.currentUser.uid
        });

        if (authContext.currentUser.uid !== commentToDelete.commentUserId) {
            toast.error('That\'s not your comment to delete!');
            return;
        }

        postCommentsRef.child(commentToDelete.key)
            .remove()
            .then(() => {
                // deleted successfully
            })
            .catch(err => {
                console.log(`Error whilst deleting comment #${commentToDelete.key}`, err);
                // TODO: handle error
            });
    };

    // return listener cleanup function
    return () => postCommentsRef.off('value', listener);
}, [slug]);

const postComment = (values) => _postCommentHandler(values);
const deleteComment = () => _deleteCommentHandler();

因为我将 currentUser 重命名为 authContext,这也需要更新:

<div>
    <img src={User} alt='Profile' style={{ width: '30px' }} />
    <span className='usertag-span'>{authContext?.currentUser?.displayName}</span>
</div>