React JS - Uncaught TypeError: this.props.data.map nie jest funkcją


111

Pracuję zreakjs i nie mogę zapobiec temu błędowi podczas próby wyświetlenia danych JSON (z pliku lub serwera):

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

Patrzyłem na:

Kod reagujący generujący „TypeError: this.props.data.map is not a function”

React.js this.props.data.map () nie jest funkcją

Żadne z nich nie pomogło mi rozwiązać problemu. Po załadowaniu mojej strony mogę sprawdzić, czy this.data.props nie jest niezdefiniowane (i ma wartość równoważną obiektowi JSON - można go wywołać window.foo), więc wygląda na to, że nie ładuje się w czasie, gdy jest wywoływany przez ConversationList. Jak się upewnić, że mapmetoda działa na danych JSON, a nie na undefinedzmiennej?

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

EDYCJA: dodawanie przykładowych konwersacji.json

Uwaga - wywołanie this.props.data.conversationsrównież zwraca błąd:

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

zwraca następujący błąd:

Uncaught TypeError: Nie można odczytać mapy właściwości undefined

Oto Conversations.json:

{"user_has_unread_messages":false,"unread_messages_count":0,"conversations":[{"id":18768,"last_message_snippet":"Lorem ipsum","other_user_id":10193}]}

Zaktualizowałem odpowiedź. Ponadto kilka zaleceń: dodaj propTypes: {data: React.PropTypes.array} do ConversationList, aby zweryfikować typ danych, i dodaj składnik componentWillUnmount: fn (), który „wyczyści” interwał w ConversationBox.
user120242

Odpowiedzi:


135

.mapFunkcja jest dostępna tylko na tablicy.
Wygląda na datato, że nie jest w oczekiwanym formacie (jest {}, ale spodziewasz się []).

this.setState({data: data});

Powinien być

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

Sprawdź, jaki typ „dane” jest ustawiany i upewnij się, że jest to tablica.

Zmodyfikowany kod z kilkoma zaleceniami (walidacja propType i 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')
    );
  }
})

32

co zadziałało dla mnie, to przekonwertowanie pliku props.data na tablicę przy data = Array.from(props.data); użyciu tej data.map()funkcji


1
Mój typ był już tablicą, np. TypeOf pokazywał właściwą rzecz również długość była poprawna, ale nadal pojawiał się błąd. To naprawiło to dla mnie. Dzięki!
user1829319

10

Mówiąc bardziej ogólnie, możesz również przekonwertować nowe dane na tablicę i użyć czegoś takiego jak concat:

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

Ten wzorzec jest faktycznie używany w aplikacji demonstracyjnej zadań do wykonania na Facebooku (patrz sekcja „Aplikacja”) na https://facebook.github.io/react/ .


1
To fajna wskazówka / sztuczka, ale chcę zauważyć, że maskowałoby to prawdziwy problem i nie rozwiązałoby OP. W przypadku PO nie pomogłoby to, ponieważ dane nadal byłyby zniekształcone (nie to, co było zamierzone). Rozmowa najprawdopodobniej zakończyłaby się pustą, ponieważ wszystkie rekwizyty byłyby zerowe. Zalecałbym nie korzystać z tej wskazówki, chyba że masz pewność, że Twoje dane są w żądanym formacie, więc nie otrzymasz nieoczekiwanego zachowania, gdy zwrócone dane nie są takie, jakie Twoim zdaniem powinny być.
user120242

1

Nie potrzebujesz do tego tablicy.

var ItemNode = this.state.data.map(function(itemData) {
return (
   <ComponentName title={itemData.title} key={itemData.id} number={itemData.id}/>
 );
});

Jak to się różni bracie?
Amar Pathak

1

Dzieje się tak, ponieważ składnik jest renderowany przed nadejściem danych asynchronicznych, należy to kontrolować przed renderowaniem.

Rozwiązałem to w ten sposób:

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>
    );
}
  • Mapa nie może rozpoznać, gdy właściwość ma wartość null / undefined, więc najpierw wykonałem kontrolę

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


0

Aby użyć mapfunkcji, musisz przekonwertować obiekt na tablicę :

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

gdzie this.props.location.statejest obiektem przekazanym do innego komponentu.


Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.