Reputation: 555
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
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]
});
}
}
dilemma.likes
is meant to be an array. You either just need to likes: dilemma.likes
or likes: [...dilemma.likes]
.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