React JS - Uncaught TypeError: this.props.data.map is not a function
Asked Answered
M

18

190

I'm working with reactjs and cannot seem to prevent this error when trying to display JSON data (either from file or server):

Uncaught TypeError: this.props.data.map is not a function

I've looked at:

React code throwing “TypeError: this.props.data.map is not a function”

React.js this.props.data.map() is not a function

Neither of these has helped me fix the problem. After my page loads, I can verify that this.data.props is not undefined (and does have a value equivalent to the JSON object - can call with window.foo), so it seems like it isn't loading in time when it is called by ConversationList. How do I make sure that the map method is working on the JSON data and not an undefined variable?

var converter = new Showdown.converter();

var Conversation = React.createClass({
  render: function() {
    var rawMarkup = converter.makeHtml(this.props.children.toString());
    return (
      <div className="conversation panel panel-default">
        <div className="panel-heading">
          <h3 className="panel-title">
            {this.props.id}
            {this.props.last_message_snippet}
            {this.props.other_user_id}
          </h3>
        </div>
        <div className="panel-body">
          <span dangerouslySetInnerHTML={{__html: rawMarkup}} />
        </div>
      </div>
    );
  }
});

var ConversationList = React.createClass({
  render: function() {

    window.foo            = this.props.data;
    var conversationNodes = this.props.data.map(function(conversation, index) {

      return (
        <Conversation id={conversation.id} key={index}>
          last_message_snippet={conversation.last_message_snippet}
          other_user_id={conversation.other_user_id}
        </Conversation>
      );
    });

    return (
      <div className="conversationList">
        {conversationNodes}
      </div>
    );
  }
});

var ConversationBox = React.createClass({
  loadConversationsFromServer: function() {
    return $.ajax({
      url: this.props.url,
      dataType: 'json',
      success: function(data) {
        this.setState({data: data});
      }.bind(this),
      error: function(xhr, status, err) {
        console.error(this.props.url, status, err.toString());
      }.bind(this)
    });
  },
  getInitialState: function() {
    return {data: []};
  },
  componentDidMount: function() {
    this.loadConversationsFromServer();
    setInterval(this.loadConversationsFromServer, this.props.pollInterval);
  },
  render: function() {
    return (
      <div className="conversationBox">
        <h1>Conversations</h1>
        <ConversationList data={this.state.data} />
      </div>
    );
  }
});

$(document).on("page:change", function() {
  var $content = $("#content");
  if ($content.length > 0) {
    React.render(
      <ConversationBox url="/conversations.json" pollInterval={20000} />,
      document.getElementById('content')
    );
  }
})

EDIT: adding sample conversations.json

Note - calling this.props.data.conversations also returns an error:

var conversationNodes = this.props.data.conversations.map...

returns the following error:

Uncaught TypeError: Cannot read property 'map' of undefined

Here is conversations.json:

{"user_has_unread_messages":false,"unread_messages_count":0,"conversations":[{"id":18768,"last_message_snippet":"Lorem ipsum","other_user_id":10193}]}
Mandamandaean answered 9/5, 2015 at 16:24 Comment(1)
I've updated the answer. Also, some recommendations: add propTypes: { data: React.PropTypes.array } to ConversationList to verify data's type, and add a componentWillUnmount: fn() that "clearInterval" the interval in ConversationBox.Homogenous
H
227

The .map function is only available on array.
It looks like data isn't in the format you are expecting it to be (it is {} but you are expecting []).

this.setState({data: data});

should be

this.setState({data: data.conversations});

Check what type "data" is being set to, and make sure that it is an array.

Modified code with a few recommendations (propType validation and clearInterval):

var converter = new Showdown.converter();

var Conversation = React.createClass({
  render: function() {
    var rawMarkup = converter.makeHtml(this.props.children.toString());
    return (
      <div className="conversation panel panel-default">
        <div className="panel-heading">
          <h3 className="panel-title">
            {this.props.id}
            {this.props.last_message_snippet}
            {this.props.other_user_id}
          </h3>
        </div>
        <div className="panel-body">
          <span dangerouslySetInnerHTML={{__html: rawMarkup}} />
        </div>
      </div>
    );
  }
});

var ConversationList = React.createClass({
 // Make sure this.props.data is an array
  propTypes: {
    data: React.PropTypes.array.isRequired
  },
  render: function() {

    window.foo            = this.props.data;
    var conversationNodes = this.props.data.map(function(conversation, index) {

      return (
        <Conversation id={conversation.id} key={index}>
          last_message_snippet={conversation.last_message_snippet}
          other_user_id={conversation.other_user_id}
        </Conversation>
      );
    });

    return (
      <div className="conversationList">
        {conversationNodes}
      </div>
    );
  }
});

var ConversationBox = React.createClass({
  loadConversationsFromServer: function() {
    return $.ajax({
      url: this.props.url,
      dataType: 'json',
      success: function(data) {
        this.setState({data: data.conversations});
      }.bind(this),
      error: function(xhr, status, err) {
        console.error(this.props.url, status, err.toString());
      }.bind(this)
    });
  },
  getInitialState: function() {
    return {data: []};
  },

 /* Taken from 
    https://facebook.github.io/react/docs/reusable-components.html#mixins
    clears all intervals after component is unmounted
  */
  componentWillMount: function() {
    this.intervals = [];
  },
  setInterval: function() {
    this.intervals.push(setInterval.apply(null, arguments));
  },
  componentWillUnmount: function() {
    this.intervals.map(clearInterval);
  },

  componentDidMount: function() {
    this.loadConversationsFromServer();
    this.setInterval(this.loadConversationsFromServer, this.props.pollInterval);
  },
  render: function() {
    return (
      <div className="conversationBox">
        <h1>Conversations</h1>
        <ConversationList data={this.state.data} />
      </div>
    );
  }
});

$(document).on("page:change", function() {
  var $content = $("#content");
  if ($content.length > 0) {
    React.render(
      <ConversationBox url="/conversations.json" pollInterval={20000} />,
      document.getElementById('content')
    );
  }
})
Homogenous answered 9/5, 2015 at 16:44 Comment(0)
H
63

You need to create an array out of props.data, like so:

data = Array.from(props.data);

then will be able to use data.map() function

Hardboard answered 17/4, 2018 at 1:43 Comment(1)
My type was an array already e.g. typeOf was showing the right thing also length was correct but still, the error was appearing. This fixed it for me. Thanks!Thieve
P
13

More generally, you can also convert the new data into an array and use something like concat:

var newData = this.state.data.concat([data]);  
this.setState({data: newData})

This pattern is actually used in Facebook's ToDo demo app (see the section "An Application") at https://facebook.github.io/react/.

Pycnidium answered 16/5, 2015 at 2:46 Comment(1)
This is a neat tip/trick, but I want to note that this would mask the real problem and would not solve OP. In the case of OP this would not help, as the data would still be malformed (not what was intended). Conversation would most likely end up empty as all props would be null. I would recommend not using this tip, unless you are sure your data is in the format you want, so you don't end up with unexpected behavior when the data returned isn't what you thought it should be.Homogenous
S
12

It happens because the component is rendered before the async data arrived, you should control before to render.

I resolved it in this way:

render() {
    let partners = this.props && this.props.partners.length > 0 ?
        this.props.partners.map(p=>
            <li className = "partners" key={p.id}>
                <img src={p.img} alt={p.name}/> {p.name} </li>
        ) : <span></span>;

    return (
        <div>
            <ul>{partners}</ul>
        </div>
    );
}
  • Map can not resolve when the property is null/undefined, so I did a control first

this.props && this.props.partners.length > 0 ?

Sandysandye answered 21/1, 2018 at 17:56 Comment(0)
U
8

I had the same problem. The solution was to change the useState initial state value from string to array. In App.js, previous useState was

const [favoriteFilms, setFavoriteFilms] = useState('');

I changed it to

const [favoriteFilms, setFavoriteFilms] = useState([]);

and the component that uses those values stopped throwing error with .map function.

Uncrown answered 17/1, 2021 at 22:31 Comment(1)
thank you! you are a brave and courageous developerLayout
H
5

Sometimes you just have to check if api call has data returned yet,

{this.props.data && (this.props.data).map(e => /* render data */)}
Hildick answered 1/5, 2021 at 5:0 Comment(0)
N
5

Create an array from props data.

let data = Array.from(props.data)

Then you can use it like this:

{ data.map((itm, index) => {
return (<span key={index}>{itm}</span>)
}}
Nadbus answered 16/2, 2022 at 9:42 Comment(0)
P
4

If you're using react hooks you have to make sure that data was initialized as an array. Here's is how it must look like:

const[data, setData] = useState([])
Prelusive answered 31/12, 2020 at 4:17 Comment(0)
R
1

You don't need an array to do it.

var ItemNode = this.state.data.map(function(itemData) {
return (
   <ComponentName title={itemData.title} key={itemData.id} number={itemData.id}/>
 );
});
Rapier answered 19/6, 2016 at 17:48 Comment(1)
How it is different bro ?Reinhard
M
1

You need to convert the object into an array to use the map function:

const mad = Object.values(this.props.location.state);

where this.props.location.state is the passed object into another component.

Milstone answered 23/5, 2020 at 11:25 Comment(0)
U
1

As mentioned in the accepted answer, this error is usually caused when the API returns data in a format, say object, instead of in an array.

If no existing answer here cuts it for you, you might want to convert the data you are dealing with into an array with something like:

let madeArr = Object.entries(initialApiResponse)

The resulting madeArr with will be an array of arrays.

This works fine for me whenever I encounter this error.

Unbending answered 30/11, 2020 at 23:56 Comment(0)
A
1

I had a similar error, but I was using Redux for state management.

My Error:

Uncaught TypeError: this.props.user.map is not a function

What Fixed My Error:

I wrapped my response data in an array. Therefore, I can then map through the array. Below is my solution.

const ProfileAction = () => dispatch => {
  dispatch({type: STARTFETCHING})
  AxiosWithAuth()
    .get(`http://localhost:3333/api/users/${id here}`)
    .then((res) => {
        // wrapping res.data in an array like below is what solved the error 
        dispatch({type: FETCHEDPROFILE, payload: [res.data]})
    }) .catch((error) => {
        dispatch({type: FAILDFETCH, error: error})
    })
 }

 export default ProfileAction
Aerodontia answered 1/12, 2020 at 19:28 Comment(0)
S
1

You should try this:

const updateNews =  async()=>{

    const res= await  fetch('https://newsapi.org/v2/everything?q=tesla&from=2021-12-30&sortBy=publishedAt&apiKey=3453452345')
    const data =await res.json();
    setArticles(data)
}
Stodge answered 30/1, 2022 at 7:42 Comment(0)
S
0

Add this line.

var conversationNodes =this.props.data.map.length>0 && this.props.data.map(function(conversation, index){.......}

Here we are just checking the length of the array. If the length is more than 0, Then go for it.

Skulk answered 8/10, 2021 at 3:40 Comment(0)
F
0
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', 'PAGE_SIZE': '2',

I delete that code line in setting it to fix it

Felipa answered 1/11, 2021 at 5:51 Comment(2)
Is this answer relevant to the question?Biocatalyst
As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.Collincolline
A
0

You might need to convert the objects into an array to use the map function since you are getting the JSON data in array form, I changed it to:

const [data, setData] = useState([]);

from:

const [data, setData] = useState(0);

I Hope, it might be helpful for you. Thanks

Antre answered 20/12, 2022 at 10:33 Comment(0)
P
0

problem->fetch products from a api/rest api using fetch or axios and display the list the error-> data.map is not a function. solution->1) map cannot be used with Array of Objects.2) Have/Must to be destructured into a const or let variable and then the Array can be used with map.

!optional- and then the data can be stored in a variable using useState hook using the setter function.

Privacy answered 21/2 at 19:34 Comment(1)
As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.Collincolline
L
-2

try componentDidMount() lifecycle when fetching data

Lockhart answered 13/7, 2018 at 17:13 Comment(1)
Also, provide some explanation, why this works and how?Godin

© 2022 - 2024 — McMap. All rights reserved.