如果在我的 MERN 应用程序中删除另一张照片后单击一张照片,我会收到 "this.props.comments.map is not a function" 或其他错误

I get "this.props.comments.map is not a function" or other errors if I click on a photo after deleting a different one in my MERN app

当我从我的应用程序中删除一张照片时,我无法在不刷新页面的情况下点击任何其他照片。 该应用程序是一个使用 Redux 在 MERN 堆栈中编写的网站。这是一个简单的照片流,您可以单击它并在其自己的页面中查看带有评论的照片。

问题是在我从流中删除一张照片后,我无法在不收到 'Cannot read property of undefined' 错误的情况下单击任何其他照片。 如果我刷新页面,我可以再次点击任何照片。 您可以访问站点 here、上传照片、删除照片,然后单击另一张照片以查看控制台中的黑屏和错误。

当我在本地 运行 应用程序时,我得到略有不同的错误,但问题似乎是空数组。有时错误会显示 'this.props.comments.map is not a function',有时会显示“TypeError:无法读取 属性 'filename' of null”,因此,出于某种原因,DOM 认为(?)照片和评论是空数组吗?
在 React 组件、Redux 动作和 reducer 以及 NodeJS 服务器之间我希望我能提供足够的信息。

由于错误是显示评论 'action' 我将从显示该功能开始。

export const individualComments = filename => (dispatch) => {
  axios
    .get(`/api/comments/comments/${filename}`)
    .then(res =>
      dispatch({
        type: INDIV_COMMENTS,
        payload: res.data
      })
    )
  .catch(err =>
    dispatch(returnErrors(err.response.data, err.response.status))
  );
}

我们仍在 Redux 中处理下一个代码块。这是此操作的减速器。案例INDIV_COMMENTS

export default (state = defaultState, action) => {
  switch (action.type) {

    case INDIV_COMMENTS:
      return {
        ...state,
        comments: action.payload
      }

      default:
      return state;    
  }
}  

接下来我们将搬出 Redux,搬出客户端。
这是在 /api.

中从服务器端路由操作调用
router.get('/comments/:filename', (req, res) => {
  Comment.find({page: req.params.filename
    }, (err, files) => {
      // Check if files
      if (!files || files.length === 0) {
        return res.json(files);
      } else if (files) {
        return res.json(files);
      }  else {
        return res.json(err);
      }
    }
  );
});  

这是其中一个错误中的 React 组件的一部分。

class Post extends React.Component {
  constructor(props) {
    super(props)
    this.submitFormHandler = this.submitFormHandler.bind(this)

  }
  componentDidMount() {
    this.props.singleView(this.props.match.params.filename);
    this.props.individualComments(this.props.match.params.filename);
    window.scrollTo(0, 0);
  }

  submitFormHandler = (e) => {
    e.preventDefault();
    const ncomment = (this.refs.aComment.value)
    const page = this.props.match.params.filename
    this.props.commentUploader(ncomment, page)
    this.refs.aComment.value = '';
  }

  render() {
    if(!this.props.comments && !this.props.photos ) {
      return (
    <div className="center">Loading...</div>
      )
    } else {
      return (

...为简洁起见,此处跳过一些 React 代码

            <h4 className="another-comment">Comments</h4>
            {this.props.comments.map(post => (
            <div className="post-card" key={post._id}>
                <div className="card-content">
                    <span className="card-title  red-text">Date:{post.date}</span>
                  <p className="comment-text-in">Comment: {post.content}</p>
                </div>
              </div>))}  

...为简洁起见,此处跳过更多 React 代码

const mapStateToProps = (state) => {
  return {
    array: state.photos.array,
    comments: state.comments.comments
  }
}

export default connect(mapStateToProps,
  { allPhotos, singleView, commentUploader,
    individualComments })(Post); 

感谢您的帮助。我希望我提供了足够的信息。

几件事:

  1. 任何时候看到 'Cannot read property of undefined' 或 'Cannot read property of null',您都应该确定为什么您正在查看的对象出乎意料 undefined/null。例如,在 Cannot read property 'filename' of null' 的情况下,您应该查看访问 filename 属性 的所有行,并确定您访问的内容为何为空。在这种情况下,问题是什么会导致 this.props.match.params 为空。
  2. 只要你看到 this.props.comments.map,你就知道 comments 不是数组(空数组或其他数组)。也许它是一个对象或一个字符串,但它可能不是 null 或未定义的(因为你会得到一些其他错误)。因此,查看父组件并确定为什么它会传递一个非空、非未定义、非数组值作为 comments prop.
  3. 我的一个突出部分是您的 API 端点。现在,如果 files 为 null、0、undefined、空字符串或 false,它将呈现为 JSON 值(这可能不是您想要的)。如果它是错误的,您可能应该 return 一个错误代码(可能是 404),因为这可能意味着该资源实际上并不存在。

不幸的是,没有足够的上下文来指出实际的错误,它很可能是多种因素的结合。我建议添加控制台日志或使用浏览器调试器来检查您从服务器返回的响应,以及在使用之前导致错误访问的那些值。您需要从那里向后工作以确定为什么这些值不是您期望的值。

我通过使用 Redux Devtools 浏览器扩展查看状态解决了这个问题。
问题出在删除文件评论时与评论相关联的文件名。 当它应该是 comments: null.
时,我将操作设置为 comments: action.payload 评论操作要求数据库对已删除的照片进行评论。这只是在幕后发生,因为浏览器中的文件名要求提供正确的(点击)照片。

  export default (state = defaultState, action) => {
    switch (action.type) {

      case INDIV_COMMENTS:
        return {
          ...state,
          comments: action.payload
        }
        default:
        return state;    
    }
  }    

改为

  export default (state = defaultState, action) => {
    switch (action.type) {

      case INDIV_COMMENTS:
        return {
          ...state,
          comments: null
        }

        default:
        return state;    
    }
  }