0

It is probably a very simple fix, but I haven't been able to figure out why the map function doesn't recognise the input as an array.

I have a Dilemma model which contains an array of user comments. I fetch the data and map it to the state of my react component. Then i try to parse the data as props to a subcomponent where i attempt to map the data.

Dilemma reducer

const initialState = {
  loading: false,
  dilemma: {}
};

export default function(state = initialState, action) {
  switch (action.type) {
    case GET_DILEMMA:
      return {
        ...state,
        dilemma: action.payload,
        loading: false
      };
    case GET_DILEMMAS:
      return {
        ...state,
        dilemmas: action.payload,
        loading: false
      };
    case CREATE_DILEMMA:
      return {
        ...state,
        dilemmas: [action.payload, ...state.dilemmas]
      };
    case DELETE_DILEMMA:
      return {
        ...state,
        dilemmas: state.dilemmas.filter(
          dilemma => dilemma._id !== action.payload
        )
      };
    case DILEMMAS_LOADING:
      return {
        ...state,
        loading: true
      };
    default:
      return state;
  }
}

this.props in the stateful component

{
  "match": { "path": "/", "url": "/", "isExact": true, "params": {} },
  "location": { "pathname": "/", "search": "", "hash": "", "key": "crpcmj" },
  "history": {
    "length": 50,
    "action": "POP",
    "location": { "pathname": "/", "search": "", "hash": "", "key": "crpcmj" }
  },
  "dilemmas": {
    "loading": false,
    "dilemma": {
      "red_votes": 0,
      "blue_votes": 0,
      "_id": "5b855fcbdfa436e0d25765fa",
      "user": "5b855f9fdfa436e0d25765f9",
      "prefix": "Hvis du skulle vælge",
      "title": "Svede remoulade",
      "red": "Gå med rustning resten af dit liv",
      "blue": "Svede remoulade resten af dit liv",
      "likes": [],
      "comments": [
        {
          "date": "2018-08-28T17:28:23.340Z",
          "_id": "5b858637b6f6a6e6218eeaba",
          "user": "5b855f9fdfa436e0d25765f9",
          "text": "This is a test3 comment",
          "author": "Albyzai"
        },
        {
          "date": "2018-08-28T17:28:19.915Z",
          "_id": "5b858633b6f6a6e6218eeab9",
          "user": "5b855f9fdfa436e0d25765f9",
          "text": "This is a test2 comment",
          "author": "Albyzai"
        },
        {
          "date": "2018-08-28T15:50:18.792Z",
          "_id": "5b856f3aed156de48a270766",
          "user": "5b855f9fdfa436e0d25765f9",
          "text": "This is a test comment",
          "author": "Albyzai"
        }
      ],
      "date": "2018-08-28T14:44:27.413Z",
      "slug": "svede-remoulade",
      "__v": 3
    }
  },
  "errors": {}
}

Stateful component

import React, { Component } from "react";
import propTypes from "prop-types";
import { connect } from "react-redux";
import { getDilemma, addLike, removeLike } from "../../actions/dilemmaActions";

import Dilemma from "./../dilemma/Dilemma";
import Divider from "./../dilemma/Divider";
import CommentFeed from "../dilemma/CommentFeed";

class DilemmaLayout extends Component {
  constructor() {
    super();
    this.state = {
      title: "",
      prefix: "",
      red: "",
      blue: "",
      red_votes: 0,
      blue_votes: 0,
      likes: [],
      comments: [],
      errors: {}
    };
  }

  componentWillReceiveProps(nextProps) {
    if (nextProps.errors) {
      this.setState({ errors: nextProps.errors });
    }

    if (nextProps.dilemmas.dilemma) {
      const dilemma = nextProps.dilemmas.dilemma;

      this.setState({
        id: dilemma._id,
        user: dilemma.user,
        title: dilemma.title,
        prefix: dilemma.prefix,
        red: dilemma.red,
        blue: dilemma.blue,
        red_votes: dilemma.red_votes,
        blue_votes: dilemma.blue_votes,
        likes: [dilemma.likes],
        comments: [dilemma.comments]
      });
    }
  }

  render() {
    return (
      <div>        
        <CommentFeed comments={this.state.comments} />
      </div>
    );
  }
}

DilemmaLayout.propTypes = {
  getDilemma: propTypes.func.isRequired,
  addLike: propTypes.func.isRequired,
  removeLike: propTypes.func.isRequired,
  dilemmas: propTypes.object.isRequired
};

const mapStateToProps = state => ({
  dilemmas: state.dilemmas,
  errors: state.errors
});

export default connect(
  mapStateToProps,
  { getDilemma, addLike, removeLike }
)(DilemmaLayout);

Functional component receiving props

import React from "react";
import Comment from "./Comment";
import { Container } from "reactstrap";

const CommentFeed = comments => {
  const commentArray = comments.map(comment => {
    <Comment author={comment.author} text={comment.text} />;
  });

  return <Container>{commentArray}</Container>;
};

export default CommentFeed;
8
  • in your CommentFeed function, console.log the comments to see what it is... Commented Aug 28, 2018 at 18:39
  • it returns {"comments":[]}, so it must be the way that i parse it to the sub component which causes the problem. Commented Aug 28, 2018 at 18:59
  • Yeah (from answer comments below) please post your reducer and so on - something is wrong with the comments/dilemmas... Commented Aug 28, 2018 at 19:13
  • I have updated the post with JSON data from the this.props in the stateful component, as well as the reducer. Commented Aug 29, 2018 at 7:57
  • Did the StackBlitz example help? I think this really comes down to some basic conditional rendering. Commented Aug 30, 2018 at 19:40

1 Answer 1

2

Try using something like ES6 destructuring assignment to extract/unpack prop comments from props on the CommentFeed component. This needs to be done because the comments array is not directly passed to the component, instead an props object is passed to the component that contains a comments property.

const CommentFeed = ({ comments }) => {
  const commentArray = comments.map(comment =>
    <Comment author={comment.author} text={comment.text} />;
  );

  return <Container>{commentArray}</Container>;
};

Or access comments such as props.comments:

const CommentFeed = props => {
  const commentArray = props.comments.map(comment =>
    <Comment author={comment.author} text={comment.text} />;
  );

  /* also an option
    const { comments } = props;
    const commentArray = comments.map(comment => {
      <Comment author={comment.author} text={comment.text} />;
    });
  */

  return <Container>{commentArray}</Container>;
};

Additionally, it's not possible to tell from what's provided, but the following setting of state could be causing issues for a couple of reasons:

componentWillReceiveProps(nextProps) {
  if (nextProps.errors) {
    this.setState({ errors: nextProps.errors });
  }

  if (nextProps.dilemmas.dilemma) {
    const dilemma = nextProps.dilemmas.dilemma;

    this.setState({
      // ...
      likes: [dilemma.likes],
      comments: [dilemma.comments]
    });
  }
}
  1. If dilemma.likes is meant to be an array. You either just need to likes: dilemma.likes or likes: [...dilemma.likes].
  2. If dilemma.comments is meant to be an array. You either just need to comments: dilemma.comments or comments: [...dilemma.comments].

There could be an issue where a nested array is being created such as [[]] which cause the error you described in the comments.map() in the CommentFeed component as map() would be trying to access property author of a nested array item like [{ "author":"Albyzai" }].author.

Given that componentWillReceiveProps will be deprecated in the future, why not just use comments and other properties coming from the store to pass to child components or render? You mentioned in comments that various props are undefined/null at the time of initial render. You will probably need to use the loading property in your store's state in combination with some sort of conditional rendering.

render() {
  const { dilemmas, errors } = this.props;

  if (errors) {
    return <div>{errors}</div>;
  } else if (!dilemmas.dilemma || dilemmas.loading) {
    return <div>Loading...</div>;
  } else {
    return <CommentFeed comments={dilemmas.dilemma.comments} />;
  }
}

I've created a StackBlitz showing this functionality in action at a basic level.

Hopefully that helps!

Sign up to request clarification or add additional context in comments.

8 Comments

Both solutions return 'cannot read property author of undefined' unfortunately.
Are you sure either comments from first example or props.comments has a value when CommentsFeed is rendered? If it does, is an array? Also const dilemma = nextProps.dilemmas.dilemma; seems problematic potentially, is nextProps.dilemmas an array? If so, you wouldn't be able to access an entry in array using . notation.
This answer solves the issue of accessing comments from props passed to the functional component. You may need to create a separate question to tackle how the comments array is processed by component Comment.
You may need to share your dilemmas prop structure in your question. It's not clear how that is structured and what is actually being set with setState() including what nextProps.dilemmas.dilemma is actually returning.
I managed to snap a screenshot of the state in the parent component: imgur.com/a/DBHg6Ft
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.