Я пытаюсь выяснить, как создать функцию очистки, как я получаю сообщение об ошибке - PullRequest
0 голосов
/ 10 марта 2020

Я пытаюсь выяснить, как создать функцию очистки, поскольку я продолжаю получать сообщение об ошибке, если я удаляю «комментарии» из зависимостей useEffect, ошибка исчезает, но затем приложение не обновляется в реальном времени, что является проблемой Если кто-то работал с 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;

1 Ответ

1 голос
/ 10 марта 2020

Не видя рассматриваемой ошибки, я могу только предположить, что это связано с тем, что использование следующего шаблона приводит к бесконечному l oop, потому что эффект запускается каждый раз, когда count изменяется:

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

Когда вы добавляете comments к своему эффекту, вы делаете то же самое.

Чтобы решить эту проблему, вы должны изменить свой эффект, чтобы полагаться на события Firebase в реальном времени, чтобы вместо этого обновлять массив комментариев. Это может быть так же просто, как изменить once('value').then((snap) => {...}) на on('value', (snap) => {...});. Поскольку теперь это прослушиватель в реальном времени, вы также должны вернуть функцию, которая отписывает подписчика изнутри вашего 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>
...