React and Firestore - problem with retrieving the user profile picture URL

1.1k Views Asked by At

So when I user posts a comment I was thinking of storing their profile picture URL on the comment object so I can access it easily, but I figured that that would not work because if they change their profile picture or if they delete it, the comment will still contain their old URL, I've also tried storing the reference to the user in Firestore, but I'm not sure if I did it wrong or what because I kept running into errors.

TLDR - I'm asking if anyone knows a way to store and access a URL (that could change in the future) for a specific comment.

Sorry if I didn't clarify or explains things as well, I'm pretty new to React as you probably already can tell. And I can try to explain things better if anyone has any questions, so yeah thanks for reading this and thanks in advance.

import React, { useEffect, useState } from 'react';
import { postComment, deleteComment } from '../../store/actions/commentsActions';
import { connect, useSelector } from 'react-redux';
import { useFirestore } from 'react-redux-firebase';
import { useForm } from 'react-hook-form';
import { toast } from 'react-toastify';
import moment from 'moment';
import { ErrorCircle } from '@styled-icons/boxicons-solid/ErrorCircle';
import { Error } from  '@styled-icons/boxicons-solid/Error';

import { Modal } from '../../helpers/Modal';
import ProfilePlaceHolder from '../../assets/images/user.svg';

import Loading from '../../helpers/Loading';

export const Comments = (props) => {

    const { auth, match, history, commentError } = props;
    const slug = match.params.slug;
    const firestore = useFirestore();
    const profile = useSelector(state => state.firebase.profile);

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

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

    useEffect(() => {

        const listener = 
        firestore
        .collection('posts')
        .doc(slug)
        .collection('comments')
        .orderBy('createdAt', 'desc')
        .onSnapshot((snapshot) => {
            let _comments = [];
            snapshot.forEach(commentSnapshot => {
                const thisComment = commentSnapshot.data();
                _comments.push({commentData: thisComment, commentId: commentSnapshot.id});
            });
            setComments(_comments);
            setLoading(false);
        }, (error) => {
            console.log(error);
        });

        return () => listener();

    }, [firestore, slug]);

    const postComment = async (formData) => {

        if (auth.isEmpty) {
            toast.error('You are not authenticated ');
            return;
        }
        await props.postComment({formData, slug});
        reset();
    };

    const deleteComment = (commentId, authorId) => {

        const currentUserId = auth.uid;
        const commentUserId = authorId;

        if (!comments) {
            return;
        }

        if (currentUserId !== commentUserId) {
            toast.error('That\'s not your comment')
            return;
        }

        props.deleteComment({commentId, authorId, slug});  
    };

    const back = () => {
        history.goBack();
    };

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



    return (
        <div className='main' style={{ width: '600px', maxWidth: '90%' }}>
            { 
                commentError !== null ? (
                <span className='error-message'>
                    <ErrorCircle size='30' style={{ marginRight: 5 }} />
                    {commentError}
                </span> ) : null
            }
            <div className='long-container' onClick={back} style={{ cursor: 'pointer', height: '50px' }}>
                Commenting on the post: {slug}
            </div>
            <div className='long-container' style={{ padding: '10px 0' }}>
                <div>
                    <img 
                        src={profile.profilePictureURL ?? ProfilePlaceHolder} 
                        alt='Profile' 
                        className='profile-picture' 
                    />
                    <span className='usertag-span'>{auth?.displayName}</span>
                </div>
                <div>
                    <form onSubmit={handleSubmit(postComment)}>
                        <textarea 
                            name='content'
                            rows='3' 
                            disabled={!auth}
                            style={{ margin: '10px 0' }}
                            placeholder='Add to the conversation!'
                            ref={register({ required: true })}
                        /> 
                        <span style={{ width: '90%' }}>
                            <button>Comment</button>
                        </span>
                    </form>
                </div>
            </div>
            {comments.map((comment) =>
            <div key={comment.commentId} className='long-container' style={{ padding: '15px 0' }}>
                <div style={{ height: '30px' }}>
                    <img 
                        src={comment.commentData.authorProfilePicture ?? ProfilePlaceHolder} 
                        alt='Profile' 
                        className='profile-picture'
                    />
                    <div className='commentMetadata' style={{ flexDirection: 'column', alignItems: 'flex-start', justifyItems: 'center' }}>
                        <span className='usertag-span'>{comment.commentData.author}</span>
                        <span>{moment(comment.commentData.createdAt?.toDate()).fromNow()}</span>
                    </div>
                </div>
                <span className='commentText-span'>
                    {comment.commentData.content}
                </span>
                <span className='commentText-span' style={{ justifyContent: 'flex-end' }}>
                    { 
                        auth.uid === comment.commentData.authorId ?
                        (
                            <Modal 
                                buttonActionClassName='delete-button' 
                                visibleButtonClassName='delete-button'
                                modalContentHeaderBackgroundColor='#fa4949'
                                title='Confirm' 
                                modalContent='Are you sure you want to delete this comment?' 
                                emoji={<Error size='30' color='#f53d3d' style={{ marginRight: 10 }} />}
                                buttonActionName='Delete'
                                buttonAction={() => deleteComment(comment.commentId, comment.commentData.authorId)}
                            />
                        ) : null
                    }
                </span>
            </div>
            )}
        </div>
    )
}

const mapDispatchToProps = (dispatch) => {
  return {
    postComment: (comment) => dispatch(postComment(comment)),
    deleteComment: (commentToDelete) => dispatch(deleteComment(commentToDelete)) 
  }
} 

const mapStateToProps = (state) => {
    return {
        auth: state.firebase.auth,
        commentError: state.commentsReducer.commentError,
    }
} 

export default connect(mapStateToProps, mapDispatchToProps)(Comments);

This is also something I tried which I mentioned, but didn't exactly work, heh

    useEffect(() => {

        const listener = firestore
        .collection('posts')
        .doc(slug)
        .collection('comments')
        .onSnapshot((snapshot) => {
            let _comments = [];
            snapshot.forEach(commentSnapshot => {
            _comments.push(commentSnapshot.data());
            setComments(_comments);

            });

            _comments.map((comment) => { 
                return comment.authorRef.get().then(snapshot => {

                    const { name, profilePictureURL } = snapshot.data();
                    setAuthorInfo({ name, profilePictureURL });
                    if (snapshot.data()) {
                        console.log(authorInfo.profilePictureURL)
                    }

            }) })

        }, (error) => {
            console.log(error);
        });

        return () => listener();

    }, [firestore, slug, comments, authorInfo]);
3

There are 3 best solutions below

0
On

Simply use this wherever you need to render the user's profile picture

<img src={auth.photo} />

and make sure you have this in your App() in App.js

useEffect(() => {
    auth.onAuthStateChanged((authUser) => {
      console.log("user is ", authUser);
      if(authUser){
        dispatch(login({
          uid: authUser.uid,
          photo: authUser.photoURL,
          email: authUser.email,
          displayName: authUser.displayName,
        }))
      }
      else{
        dispatch(logout());
      }
    })
  }, [dispatch])

3
On

I'm not a expert with React but maybe using DocumentReference could be helpful to refer to the URL stored in Firestore. Here you could find the docs to make this work. I imagine that the URL is linked with the user ID, so you can also use this to get the image URL.

2
On

I'm not sure if you can get information for a Firebase user that isn't the current logged in user.

What I suggest is storing info for each of your users in your database, then every time a user is logged in, you can update your database if necessary. Then when showing a user's comment, you lookup the picture in your database for that user.