React JS - Uncaught TypeError: this.props.data.map no es una función

110

Estoy trabajando con reactjs y parece que no puedo evitar este error al intentar mostrar datos JSON (ya sea del archivo o del servidor):

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

He mirado:

Reaccionar código arrojando "TypeError: this.props.data.map no es una función"

React.js this.props.data.map () no es una función

Ninguno de estos me ha ayudado a solucionar el problema. Después de que se cargue mi página, puedo verificar que this.data.props no está indefinido (y tiene un valor equivalente al objeto JSON - puede llamar con window.foo), por lo que parece que no se está cargando a tiempo cuando lo llama ConversationList. ¿Cómo me aseguro de que el mapmétodo funcione en los datos JSON y no en una undefinedvariable?

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')
    );
  }
})

EDITAR: agregar conversaciones de muestra.json

Nota: la llamada this.props.data.conversationstambién devuelve un error:

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

devuelve el siguiente error:

Error de tipo no detectado: no se puede leer la propiedad 'mapa' de indefinido

Aquí está conversations.json:

{"user_has_unread_messages":false,"unread_messages_count":0,"conversations":[{"id":18768,"last_message_snippet":"Lorem ipsum","other_user_id":10193}]}
pulsaciones de teclas
fuente
Actualicé la respuesta. Además, algunas recomendaciones: agregue propTypes: {data: React.PropTypes.array} a ConversationList para verificar el tipo de datos, y agregue un componentWillUnmount: fn () que "clearInterval" el intervalo en ConversationBox.
user120242

Respuestas:

134

La .mapfunción solo está disponible en array.
Parece que datano está en el formato que esperabas (está {} pero esperas []).

this.setState({data: data});

debiera ser

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

Compruebe qué tipo de "datos" se está configurando y asegúrese de que sea una matriz.

Código modificado con algunas recomendaciones (validación propType y 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')
    );
  }
})
user120242
fuente
32

lo que funcionó para mí es convertir props.data en una matriz usando data = Array.from(props.data); entonces podría usar la data.map()función

Vishal Seshagiri
fuente
1
Mi tipo ya era una matriz, por ejemplo, typeOf mostraba lo correcto y la longitud era correcta, pero aún así, aparecía el error. Esto me lo arregló. ¡Gracias!
user1829319
10

De manera más general, también puede convertir los nuevos datos en una matriz y usar algo como concat:

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

Este patrón se utiliza en realidad en la aplicación de demostración ToDo de Facebook (consulte la sección "Una aplicación") en https://facebook.github.io/react/ .

Bresson
fuente
1
Este es un buen consejo / truco, pero quiero señalar que esto enmascararía el problema real y no resolvería OP. En el caso de OP, esto no ayudaría, ya que los datos aún estarían mal formados (no es lo que se pretendía). La conversación probablemente terminaría vacía ya que todos los accesorios serían nulos. Recomendaría no usar este consejo, a menos que esté seguro de que sus datos están en el formato que desea, para que no termine con un comportamiento inesperado cuando los datos devueltos no sean los que pensaba que deberían ser.
user120242
1

No necesitas una matriz para hacerlo.

var ItemNode = this.state.data.map(function(itemData) {
return (
   <ComponentName title={itemData.title} key={itemData.id} number={itemData.id}/>
 );
});
CodeGuru
fuente
¿Cómo es diferente hermano?
Amar Pathak
1

Sucede porque el componente se procesa antes de que lleguen los datos asíncronos, debe controlar antes de procesar.

Lo resolví de esta manera:

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>
    );
}
  • El mapa no se puede resolver cuando la propiedad es nula / indefinida, así que hice un control primero

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

JC
fuente
0

Necesita convertir el objeto en una matriz para usar la mapfunción:

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

donde this.props.location.stateestá el objeto pasado a otro componente.

ISRAEL ODUGUWA
fuente
-2

prueba el componentDidMount()ciclo de vida al recuperar datos

Arif Ramadhani
fuente
5
Además, proporcione alguna explicación, ¿por qué funciona esto y cómo?
Mittal Patel