2017-10-23 2 views
0

Ich habe diese Komponente, welche Rolle ist, um ein Kategorie-Objekt von Server mit Fetch, die ich in ComponentWillMount setzen. Es gibt nur einen setState: Es ist im ComponentWillMount-Callback, wenn die Daten vom Server ankommen.react Komponente rendert zweimal mit nur einem setState in componentWillMount

Das Problem ist, dass die Komponente zweimal rendert, zum ersten Mal this.state.category Wert ist der Ausgangszustand des Konstruktors, dann das zweite Mal this.state.category gleich dem Objekt vom Server erhalten. Wie kann ich die Rendermethode nur einmal aufrufen, direkt nach dem setState?

import ReactDom from 'react-dom'; 
import React from 'react'; 
import Breadcrumb from './Breadcrumb'; 


export default class Category extends React.Component{ 

constructor(props){ 
    super(props); 
    this.state = { 
     category: { 
      'published_projects':[{'images':[]}], 
      'unpublished_projects':[{'images':[]}], 
     } 
    }; 

} 

componentWillMount(){ 
    fetch(route('api.categories.show', {'slug':this.props.match.params.slug})) 
     .then(response => response.json()) 
     .then(data => {this.setState({category: data});}); 
} 

totalLikesCount(){ 
    return this.state.category.published_projects.reduce((accu, item) => 
     accu + item.likes_count ,0 
    ); 
} 
totalViewsCount(){ 
    return this.state.category.published_projects.reduce((accu, item) => 
     accu + item.views_count ,0 
    ); 
} 

totalPicturesCount(){ 
    return this.state.category.published_projects.reduce((accu, item) => 
     accu + item.images.length ,0 
    ); 
} 

render(){ 
    const category = this.state.category; 
    console.log(category); 
    return (
     <div className="w980px center"> 

      <div className="CategoryHeader-bg-wrapper margin-rt10 margin-lt10"> 
       <img alt="" src={category.picture_url}/> 
       <div className="CategoryHeader-bg-gradient"> 
        <h1> 
         { category.name } 
        </h1> 
       </div> 
      </div> 
      <div className="CategoryHeader-metrics-wrapper u-boxShadow margin-rt10 margin-lt10"> 
       <ul className="CategoryHeader-metrics-list"> 
        <li className="CategoryHeader-metrics-item txt-center"> 
         <span className="CategoryHeader-metrics-label"> 
          Projets 
         </span> 
         <span className="CategoryHeader-metrics-value"> 
          { category.published_projects.length } 
         </span> 
        </li> 
        <li className="CategoryHeader-metrics-item txt-center"> 
         <span className="CategoryHeader-metrics-label"> 
          Total de mentions j'aime 
         </span> 
         <span className="CategoryHeader-metrics-value"> 
          { this.totalLikesCount() } 
         </span> 
        </li> 
        <li className="CategoryHeader-metrics-item txt-center"> 
         <span className="CategoryHeader-metrics-label"> 
          Total des vues 
         </span> 
         <span className="CategoryHeader-metrics-value"> 
          { this.totalViewsCount() } 
         </span> 
        </li> 
        <li className="CategoryHeader-metrics-item txt-center"> 
         <span className="CategoryHeader-metrics-label"> 
          Total des photos 
         </span> 
         <span className="CategoryHeader-metrics-value"> 
          { this.totalPicturesCount() } 
         </span> 
        </li> 
       </ul> 
      </div> 
     </div> 
    ); 
} 

}

+1

Den einzigen Weg, dies zu tun wäre, um die Datenverbindung in einer übergeordneten Komponente zu machen und Mounten Sie die Komponente "Category" erst, wenn die Daten vorhanden sind. Sie können das anfängliche Rendern der Komponente nicht verhindern. Sie können überprüfen, ob die Daten in die render-Methode geladen werden, und "null zurückgeben", wenn sie nicht existiert, aber die Funktion "render" wird immer noch zweimal aufgerufen. –

Antwort

3

Code könnte zum Rendern zweimal fragen, indirekt:

// This calls to setState (but after a while) 
fetch(route('api.categories.show', {'slug':this.props.match.params.slug})) 
    .then(response => response.json()) // These are not called yet 
    .then(data => {this.setState({category: data});}); 

// Then immediately after calling fetch, it gets to the next stages of the react lifecycles, 
// (like render) 

render() ... 

// Then setState gets called because it is an asynchronous call, 
.then(data => {this.setState({category: data});}); 

// and setState calls to render again. 

Das Problem ist, dass die Komponente zweimal

macht Sie haben eine hässliche , nicht empfohlene Option, die return null vonist Das erste Mal (wenn keine Daten abgerufen werden) und normal zurückkehren, wenn Sie die Daten haben, und verhindern, dass das Element gerendert wird.

Eine andere Option, die Sie haben, besteht darin, zu erfassen, ob die Daten im übergeordneten Element abgerufen werden oder nicht, und das Element dort auszublenden.

0

Es gibt keine Möglichkeit zu Verzögerung die eines machenMethode aufrufen, bis die Daten zurückkommt.

Die Komponente wartet nicht, bis Ihre asynchronen Vorgänge abgeschlossen sind, bevor das Rendern ausgeführt wird. Daher werden Sie mindestens haben 2 machen Anrufe:

  • One, wenn die Komponente erste Halterungen
  • Zweitens, wenn der Asynchron-Vorgang abgeschlossen und der Rückruf mit setState führt

jedoch, was Sie tun können, ist Verhindern Sie, dass die Komponente während des ersten Renderaufrufs zum DOM hinzugefügt wird, indem Sie null zurückgeben.

Sie können dies implementieren, indem eine Lade Flagge auf Ihren Zustand Hinzufügen und blätterte es dann, wenn die Daten zurückkommt ... und dann machen Methode, um so etwas wie dies ändern:

render() { 
    if (this.state.loading) return (null); 
    return (
     <div className="w980px center"> 
     ... 
     </div> 
    ); 
} 
1

Basierend auf Leben Reagieren Zyklus die componentWillMount Funktion wird vor dem Rendern aufgerufen ... aber dies bedeutet nicht, dass Render-Funktion auf einen async Aufruf innerhalb componentWillMount warten, um Rendering zu starten, so wird Ihre Komponente einmal vor dem Abruf fertig und einmal nach dem Abruf durchgeführt wird.

Wenn Sie sicherstellen möchten, dass Ihre Komponente nur dann rendert, wenn die Daten verfügbar sind, können Sie Daten in der übergeordneten Komponente abrufen, diese Komponente nur dann rendern, wenn Daten verfügbar sind, und sie dann als Props an diese Komponente übergeben Garantie ein einzelnes render

1

Das Problem hier könnte sein, dass fetch dauert einige Zeit, um die Daten vom Server zu laden, und daher wird das DOM innerhalb dieser Zeit geladen und wird aktualisiert, sobald die fetch ed Daten zurückgebracht werden.

Um sicherzustellen, dass die Komponente nur einmal geladen wird, fügen Sie eine neue Statusvariable, isLoaded oder etwas, hinzu und machen Sie sie falsch. Wenn Ihre fetch fertig ist, legen Sie sie auf true fest und rendern die Komponente nur, wenn isLoaded wahr ist.

Hier ist ein Codebeispiel:

constructor(props){ 
    super(props); 
    this.state = { 
     isLoaded: false, 
     category: { 
      'published_projects':[{'images':[]}], 
      'unpublished_projects':[{'images':[]}], 
     } 
    }; 
} 

componentWillMount(){ 
    fetch(route('api.categories.show', {'slug':this.props.match.params.slug})) 
    .then(response => response.json()) 
    .then(data => {this.setState({category: data, isLoaded: true});}); 
} 

und schließlich in der render Methode, einfach einen if (this.state.isLoaded) { return(...); } else { return <p>Loading categories...</p>; }

+0

ok gute Idee, danke. nur ein Detail: Sie können keine if-Anweisung in render-Methode machen, aber Sie können ein ternaire tun. –

+1

nein, Sie können eine 'if' Anweisung innerhalb der' render' Methode machen, einfach nicht innerhalb des 'return' Tags – Indranil

Verwandte Themen