J'ai un problème avec l'un de mes composants React. Je pense que AJAX ne charge pas tout le contenu du serveur externe avant que React ne rende le composant ChildComp
.
Ci-dessus, vous pouvez voir l’arbre de réponse provenant du serveur. Et voici le code de mon composant:
var ChildComp = React.createClass({
getInitialState: function(){
return {
response: [{}]
}
},
componentWillReceiveProps: function(nextProps){
var self = this;
$.ajax({
type: 'GET',
url: '/subscription',
data: {
subscriptionId: nextProps.subscriptionId //1
},
success: function(data){
self.setState({
response: data
});
console.log(data);
}
});
},
render: function(){
var listSubscriptions = this.state.response.map(function(index){
return (
index.id
)
});
return (
<div>
{listSubscriptions}
</div>
)
}
});
Cela fonctionne très bien, mais si je change mon retour à:
return (
index.id + " " + index.order.id
)
l'identifiant n'est pas défini. Et toutes les autres propriétés de l'objet order. Pourquoi est-ce comme ça? Si je console.log
ma réponse après la fonction success
, il donne toutes les données (comme dans l'image). Mon hypothèse est que seuls les premiers objets sont chargés lorsque React rend le composant, puis tous les autres objets internes sont chargés. Je ne peux pas vraiment dire si c'est le cas (ça a l'air si bizarre) ni comment résoudre ce problème.
J'ai aussi essayé quelque chose comme ça
success: function(data){
self.setState({
response: data
}, function(){
self.forceUpdate();
})
}.bind(this)
mais le rendu est toujours effectué avant que ma console.log(data)
ne soit déclenchée. Comment attendre la réponse AJAX et uniquement après avoir rendu le composant?
Voici votre code retravaillé avec quelques commentaires des pièces modifiées
getInitialState: function(){
return {
// [{}] is weird, either use undefined (or [] but undefined is better).
// If you use [], you loose the information of a "pending" request, as
// you won't be able to make a distinction between a pending request,
// and a response that returns an empty array
response: undefined
}
},
loadSubscriptionData: function(subscriptionId){
var self = this;
// You probably want to redo the ajax request only when the
// subscriptionId has changed (I guess?)
var subscriptionIdHasChanged =
(this.props.subscriptionId !== subscriptionId)
if ( subscriptionIdHasChanged ) {
// Not required, but can be useful if you want to provide the user
// immediate feedback and erase the existing content before
// the new response has been loaded
this.setState({response: undefined});
$.ajax({
type: 'GET',
url: '/subscription',
data: {
subscriptionId: subscriptionId //1
},
success: function(data){
// Prevent concurrency issues if subscriptionId changes often:
// you are only interested in the results of the last ajax
// request that was made.
if ( self.props.subscriptionId === subscriptionId ) {
self.setState({
response: data
});
}
}
});
}
},
// You want to load subscriptions not only when the component update but also when it gets mounted.
componentDidMount: function(){
this.loadSubscriptionData(this.props.subscriptionId);
},
componentWillReceiveProps: function(nextProps){
this.loadSubscriptionData(nextProps.subscriptionId);
},
render: function(){
// Handle case where the response is not here yet
if ( !this.state.response ) {
// Note that you can return false it you want nothing to be put in the dom
// This is also your chance to render a spinner or something...
return <div>The responsive it not here yet!</div>
}
// Gives you the opportunity to handle the case where the ajax request
// completed but the result array is empty
if ( this.state.response.length === 0 ) {
return <div>No result found for this subscription</div>;
}
// Normal case
var listSubscriptions = this.state.response.map(function(index){
return (
index.id
)
});
return (
<div>
{listSubscriptions}
</div>
)
}
Tout d’abord, vous voudriez placer l’appel AJAX dans componentWillMount()
ou componentDidMount()
(si vous devez effectuer une manipulation DOM).
componentWillReceiveProps()
est
Appelé lorsqu'un composant reçoit de nouveaux accessoires. Cette méthode n'est pas appelée pour le rendu initial.
Mais même si vous placez l'appel AJAX dans componentWillMount()
ou componentDidMount()
, render()
sera probablement appelé avec une réponse vide avant la fin de l'appel de AJAX.
Ainsi, l'ordre d'appel serait le suivant (j'ai supposé que vous avez mis l'appel AJAX dans componentWillMount()
:
componentWillMount()
(un appel AJAX est déclenché) -> componentDidMount()
-> render()
(restituez avec une réponse vide; index.order.id
provoquera une erreur) -> AJAX appel renvoie et appelle this.setState()
-> (d'autres méthodes de cycle de vie composant telles que shouldComponentUpdate()
appelé; voir le lien FB ci-dessus pour plus de détails) -> render()
est appelé à nouveau.
La solution à votre problème est donc:
1) Déplacement de l’appel AJAX vers componentWillMount()
ou componentDidMount()
2) Définissez l’état initial uniquement sur []
(par opposition à [{}]
) ou vérifiez si index
est vide dans
var listSubscriptions = this.state.response.map(function(index){
return (
index.id
)
});
Je l'ai jeté dans un JSBin et je n'arrive pas à reproduire votre problème.
http://jsbin.com/jefufe/edit?js,output
Chaque fois que le bouton est cliqué, il envoie de nouveaux accessoires à <ChildComp/>
, qui déclenche ensuite sa componentWillReceiveProps
, envoie la demande (fictive) ajax, reçoit la réponse (fictive), définit l'état de la réponse sur la réponse (fictive) et retransforme le composant avec les données correctes affichées.
La seule chose que j'ai ajoutée était un contrôle de index.id == null
avant d'essayer d'y accéder, mais je ne pense pas que cela puisse résoudre le problème de l'impossibilité d'accéder à index.order.id
.
La seule raison pour laquelle je peux expliquer pourquoi vous ne pouvez pas accéder à index.order.id
est peut-être parce que vous avez un code que vous ne nous montrez pas qui modifie la réponse à un moment donné.
N'oubliez pas non plus que les composants de React seront toujours restitués lorsque son état ou ses accessoires ont changé, à moins que vous n'ayez défini le comportement dans shouldComponentUpdate
qui indique le contraire. Cela signifie que lorsque vous recevez l'objet entier du serveur et que vous le définissez à un état de votre composant, celui-ci sera rendu à nouveau et vous montrera l'état mis à jour. Il n’existe aucune raison/moyen pour React de "rendre superficiellement" votre objet de réponse, comme vous le suggériez dans votre message original.
Je ne suis pas sûr que cette réponse aide, mais j'espère que vous pourrez consulter le JSBin que j'ai fourni et trouver quelque chose que vous auriez peut-être oublié.
Il suffit de faire l'appel AJAX dans componentDidMount()
et dans le rendu, vous pouvez simplement vérifier ...
if(this.state.response === '' || this.state.response === [] || this.state.response === undefined)
return <Loader />
else{
// do your stuff here
}
OU si vous utilisez Rea 16.6 ou plus, vous pouvez simplement utiliser des composants paresseux.