Jonas Mohr Pedersen
Jonas Mohr Pedersen

Reputation: 555

React Array.map does not recognise JSON data as array

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;

Upvotes: 0

Views: 258

Answers (1)

Alexander Staroselsky
Alexander Staroselsky

Reputation: 38757

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!

Upvotes: 2

Related Questions