Search code examples
reactjsreact-reduxreact-hooksreact-redux-firebaseredux-firestore

Error rendering react-redux functional component when data is deleted from firestore (useFirestoreConnect)


My ArticleList component is successfully getting & displaying the user's list of articles from firestore when I first load the app. The user can click a "Remove Article" button, which successfully removes the article from the subcollection in firestore, but it causes an error in the rendering of the react component, which seems to still be trying to render the article that was just removed and is now null. Is there something else I can do to make my react component continuously listen to the firestore data? If possible, I'd like to keep this a functional component and use hooks rather than making it a class, but I'm still learning how to use react hooks and therefore struggling a bit.

ArticleList component:

const ArticleList = (props) => {
const firestore = useFirestore();
const userId = props.auth.uid;
useFirestoreConnect([
{
  collection: 'users',
  doc: userId,
  subcollections: [{collection: 'articles'}],
  storeAs: userId + '::articles'
}
]);
const myArticles = useSelector(state => state.firestore.data[`${userId}::articles`]);
const dispatch = useDispatch();
const removeArticle = useCallback(
articleId => dispatch(removeArticleFromFirebase({ firestore }, articleId)),
[firestore]
);
if (props.auth.uid) {
return(
  <div>
    <h3>My Articles</h3>
    <p>Currently signed in: {props.auth.email}</p>
    <br/>
    {myArticles ? (
        Object.keys(myArticles).map(articleId => {
          let article = myArticles[articleId];
          let articleInformation = '';
          if (articleId === props.currentPaperId) {
            articleInformation =
              <div>
                <p>{article.year}</p>   
                <p>{article.description}</p>
                <a target="_blank" href={article.downloadUrl}><button className='waves-effect waves-light btn-small'>See article</button></a>
                <button className='waves-effect waves-light btn-small' onClick={() => {removeArticle(articleId);}}>Remove from My Articles</button>
              </div>;
          }
          let authorName = '';
          if (article.author) {
            authorName = ` by ${article.author}`;
          }
          if (article) {
            return <span key={articleId}>
              <li onClick={() => {dispatch(selectArticle(articleId));}}>
                <em>{article.title}</em>{authorName}
              </li>{articleInformation}
            </span>;
          } else {
            return null;
          }
        })
      ) : (
        <h4>No articles yet</h4>
      )
    }
  </div>
);
} else {
  return null;
}
};
const mapStateToProps = (state) => {
  return {
    currentPaperId: state.currentPaperId,
    auth: state.firebase.auth
  };
};
export default compose(connect(mapStateToProps))(ArticleList);

And the removeArticleFromFirebase action:

export const removeArticleFromFirebase = ({ firestore }, id) => {
return (dispatch, getState) => {
const userId = getState().firebase.auth.uid;
firestore
  .collection('users')
  .doc(userId)
  .collection('articles')
  .doc(id)
  .delete()
  .then(() => {
    console.log('Deleted article from firestore: ', id);
    dispatch({ type: 'REMOVE_ARTICLE', id });
  })
  .catch(err => {
    console.log('Error: ', err);
  });
};
}

I've tried adding useState and useEffect in the ArticleList as follows (and tried having the component's return statement map through myArticlesState instead of myArticles), but no success:

const [myArticlesState, setMyArticlesState] = useState(myArticles);
useEffect(() => {
setMyArticlesState(myArticles);
}, [myArticles]);

Note: I do not currently have this article list in overall app state/redux store/props at all. This is something I was thinking of trying next, but I decided to post my question first in case I can just use hooks in this component. No other components/parts of the app need access to this particular list.

Console errors: error image 1 error image 2 Github repo: https://github.com/jpremmel/yarp2.0


Solution

  • It's kind of difficult to see what's going on but it appears as though you are trying to use a property on an object that does not exist. Therefore, checking for those properties should help resolve this.

    Can you try the follow code as your ArticleList?

    const ArticleList = (props) => {
      const firestore = useFirestore();
      const userId = props.auth.uid;
    
      useFirestoreConnect([{
        collection: 'users',
        doc: userId,
        subcollections: [{ collection: 'articles' }],
        storeAs: userId + '::articles'
      }]);
    
      const myArticles = useSelector(state => state.firestore.data[`${userId}::articles`]);
      const dispatch = useDispatch();
      const removeArticle = useCallback(articleId => dispatch(removeArticleFromFirebase({ firestore }, articleId)), [firestore]);
    
      if (props.auth.uid) {
        return (
          <div>
            <h3>My Articles</h3>
            <p>Currently signed in: {props.auth.email}</p>
            <br />
            {myArticles ? (
              Object.keys(myArticles).map(articleId => {
                let article = myArticles[articleId];
                let articleInformation = '';
    
                if (article) {
                  if (
                    articleId === props.currentPaperId &&
                    article.hasOwnProperty('year') &&
                    article.hasOwnProperty('description') &&
                    article.hasOwnProperty('downloadUrl')
                  ) {
                    articleInformation =
                      <div>
                        <p>{article.year}</p>
                        <p>{article.description}</p>
                        <a target="_blank" href={article.downloadUrl}><button className='waves-effect waves-light btn-small'>See article</button></a>
                        <button className='waves-effect waves-light btn-small' onClick={() => { removeArticle(articleId); }}>Remove from My Articles</button>
                      </div>;
                  }
    
                  let authorName = '';
                  if (article.hasOwnProperty('author') && article.author) {
                    authorName = ` by ${article.author}`;
                  }
    
                  if (article.hasOwnProperty('title') && article.title) {
                    return <span key={articleId}>
                      <li onClick={() => { dispatch(selectArticle(articleId)); }}>
                        <em>{article.title}</em>{authorName}
                      </li>{articleInformation}
                    </span>;
                  } else {
                    return null;
                  }
                }
              })
            ) : (
                <h4>No articles yet</h4>
              )
            }
          </div>
        );
      } else {
        return null;
      }
    };
    const mapStateToProps = (state) => {
      return {
        currentPaperId: state.currentPaperId,
        auth: state.firebase.auth
      };
    };
    export default compose(connect(mapStateToProps))(ArticleList);