2016-04-20 7 views
4

Ich bin ziemlich neu zu ReactJS und meine Probleme sind ziemlich ungewöhnlich, kann auftreten, einfach weil ich Dinge nicht so implementieren, wie sie sein sollen.Reagieren Js Komponente Rendering nur einmal nach Zustandsänderung

Also im Prinzip funktionierte das vorher gut, aber ich musste einige neue Funktionen hinzufügen, und ... nun, etwas ist aus.

Zu Beginn - ConvFrame ist Top-Komponente, auf der Seite an der Spitze erscheinen, und es besteht aus ConvForm Komponente (neue Abfragen hinzufügen) und ConvList wo nicht zugeordnet und neue Anrufe gehen. Die ConvList hat hier ID und Key von 1.

Es gibt auch Liste der Arbeitnehmer unter, die sie verwenden ConvForm nur die Felder selbst Dropzones sind, die schnell neuen Anruf Aufgaben zuweisen lassen. Die ConvList hier haben Id und Key gleich ID des Worker.

Da der ConvList gerendert wird, fragt er den Server nach Jobs in der Liste ab. Und das funktioniert für alle gut. Es scheint jedoch ein seltsames Problem zu geben, wenn ein neuer Artikel über ConvForm hinzugefügt wird. Er fordert handleCommentSubmit() Funktion ruft this.loadCommentsFromServer(); (dumm, ich weiß!) Und dann für Datensätze neuen Staat setzt this.setState({records: data});

Als erster Datensatz die /api/zlecenia hinzugefügt wird, wird zweimal aufgerufen. Einmal von der loadCommentsFromServer() innerhalb ConvFrame und zweiten Mal von innerhalb ConvList. Hinzufügen von zweiten Datensatz per Formular ruft es einmal auf, die Komponente scheint nicht auf die Zustandsänderung zu reagieren. Etwas ist schlecht implementiert, denke ich.

Hier ist der Quellcode: Conversations.js

//For dragging 
var placeholder = document.createElement("div"); 
placeholder.className = "placeholder"; 
var dragged; 
var over; 

/** 
* Conversation 
* Should be used for listing conversation blocks, adds class based on age of task. 
* Detects drag events, renders block, calls dragEnd function to append block to new 
* location and uses props.OnDrop function to pass id of element and target id of worker 
*/ 
window.Conversation = React.createClass({ 
    dynamicClass: function() { 
     return "convo " + this.props.delay; 
    }, 
    dragStart: function (e) { 
     dragged = e.currentTarget; 
     over = null; 
     e.dataTransfer.effectAllowed = 'move'; 
     e.dataTransfer.setData("text/html", e.currentTarget); 
    }, 
    dragEnd: function (e) { 
     $(dragged).show(); 
     $(placeholder).remove(); 
      console.log(over, over.className); 
     if (over && over.className === "candrop") { 
      var id = Number(dragged.dataset.id); 

      this.props.onDrop({id: id, target: over.id}); 
      over.appendChild(dragged); 
     }else{ 
      console.log('returning base:' + over); 
      $(dragged).parent().append(dragged); 
     } 
    }, 
    render: function() { 
    return (
     <div draggable="true" data-id={this.props.id} onDragEnd={this.dragEnd} onDragStart={this.dragStart} className={this.dynamicClass()} > 
     {this.props.children} 
     </div> 
    ); 
    } 
}); 

/** 
* ConvList 
* Displays conversation dropdown list. I should aim to make it single component, do not repeat for workers. 
* Detects dragOver for .candrop and place some funny placeholder. Detect position change from Conversation view 
* call master class from parent component and pass data. Detect delete event. 
*/ 
window.ConvList = React.createClass({ 
    getInitialState: function() { 
     return {data: []}; 
    }, 
    loadConvsFromServer: function() { 
     $.ajax({ 
      url: baseUrl + '/api/zlecenia', 
      type: 'GET', 
      data: {id: this.props.id}, 
      success: function (data) { 
       this.setState({data: data}); 
      }.bind(this), 
      error: function (xhr, status, err) { 
       console.error(this.props.url, status, err.toString()); 
      }.bind(this) 
     }); 
    }, 
    componentDidMount: function() { 
     this.loadConvsFromServer(); 
    }, 
    dragOver: function (e) { 
     e.preventDefault(); 
     $(dragged).fadeOut(); 
     if (e.target.className === "candrop") { 
      if (e.target.className == "placeholder") 
       return; 
      over = e.target; 
      e.target.appendChild(placeholder, e.target); 
     } 
    }, 
    updatePosition: function (data) { 
     console.log('update convo %d for member %e', data.id, data.target); 

     $.ajax({ 
      url: baseUrl + '/api/zlecenia', 
      type: 'PUT', 
      data: {id: data.id, assign: data.target}, 
      success: function (data) { 
      }.bind(this), 
      error: function (xhr, status, err) { 
       console.error(this.props.url, status, err.toString()); 
      }.bind(this) 
     }); 
    }, 
    deleteTask: function (e) { 
     var taskIndex = parseInt(e.target.value, 10); 
     console.log('remove task: %d', taskIndex); 

     $.ajax({ 
      url: baseUrl + '/api/zlecenia/' + taskIndex, 
      type: 'DELETE', 
      success: function (data) { 
       this.loadConvsFromServer(); 
      }.bind(this), 
      error: function (xhr, status, err) { 
       console.error(this.props.url, status, err.toString()); 
      }.bind(this) 
     }); 

    }, 
    render: function() { 
     return (
     <div className="convlist" onDragOver={this.dragOver}> 
      <div className="candrop" id={this.props.id} >{this.props.id} 
       {this.state.data.map((c) => 
        <Conversation onDrop={this.updatePosition} delay={c.delayTime} id={c.id} key={c.id}> 
         <p className="convTop">{c.formattedTime} | Tel. {c.phone} | {c.name} | Nr.Rej {c.number}</p> 
         <p>{c.text}</p> 
         <button className="deleteConv" onClick={this.deleteTask} value={c.id}>x</button> 
        </Conversation> 
       )} 
      </div> 
     </div> 

    ); 
    } 
}); 

/** 
* ConvForm 
* Displays conversation create form. Prepares fields, validates them. 
* Call master function to add new record on send. 
*/ 
var ConvForm = React.createClass({ 
    getInitialState: function() { 
     return {phone: '', name: '', number: '', text: ''}; 
    }, 
    handlePhoneChange: function (e) { 
     this.setState({phone: e.target.value}); 
    }, 
    handleNameChange: function (e) { 
     this.setState({name: e.target.value}); 
    }, 
    handleNumberChange: function (e) { 
     this.setState({number: e.target.value}); 
    }, 
    handleTextChange: function (e) { 
     this.setState({text: e.target.value}); 
    }, 
    submitForm: function (e) { 
     e.preventDefault(); 
     var phone = this.state.phone.trim(); 
     var name = this.state.name.trim(); 
     var number = this.state.number.trim(); 
     var text = this.state.text.trim(); 

     if (!text || !phone || !name || !number) { 
      return; 
     } 
     this.props.onConvSubmit({phone: phone, name: name, number: number, text: text}); 
     this.setState({phone: '', text: '', number: '', name: ''}); 
    }, 
    render: function() { 
     return (
     <form className="convForm" onSubmit={this.submitForm}> 
      <div className="row"> 
       <div className="col-xs-12 col-md-4"> 
        <div className="form-group"> 
         <input 
         className="form-control" 
         type="text" 
         placeholder="Telefon" 
         value={this.state.phone} 
         onChange={this.handlePhoneChange} 
         /> 
        </div> 
       </div> 
       <div className="col-xs-12 col-md-4"> 
        <div className="form-group"> 
         <input 
         className="form-control" 
         type="text" 
         placeholder="Imię i nazwisko" 
         value={this.state.name} 
         onChange={this.handleNameChange} 
         /> 
        </div> 
       </div> 
       <div className="col-xs-12 col-md-4"> 
        <div className="form-group"> 
         <input 
         className="form-control" 
         type="text" 
         placeholder="Nr. rejestracyjny" 
         value={this.state.number} 
         onChange={this.handleNumberChange} 
         /> 
        </div> 
       </div> 
      </div> 
      <div className="form-group"> 
       <textarea 
       className="form-control" 
       type="text" 
       placeholder="Treść" 
       value={this.state.text} 
       onChange={this.handleTextChange} 
       /> 
      </div> 
      <input className="btn btn-success" type="submit" value="Zapisz" /> 
     </form> 
       ); 
    } 
}); 

/** 
* ConvFrame 
* Conversation main frame and root functions for both form and conversations listing. 
*/ 
window.ConvFrame = React.createClass({ 
    getInitialState: function() { 
     return {records: []}; 
    }, 
    loadCommentsFromServer: function() { 
     $.ajax({ 
      url: baseUrl + '/api/zlecenia', 
      type: 'GET', 
      data: {id : 1}, 
      success: function (data) { 
       this.setState({records: data}); 
      }.bind(this), 
      error: function (xhr, status, err) { 
       console.error(this.props.url, status, err.toString()); 
      }.bind(this) 
     }); 
    }, 
    handleCommentSubmit: function (convo) { 
     $.ajax({ 
      url: baseUrl + '/api/zlecenia', 
      type: 'POST', 
      data: convo, 
      success: function (data) { 
       this.loadCommentsFromServer(); 
      }.bind(this), 
      error: function (xhr, status, err) { 
       console.error(this.props.url, status, err.toString()); 
      }.bind(this) 
     }); 
    }, 
    render: function() { 
     return (
       <div className="add-new"> 
         <div className="row"> 
          <div className="col-xs-12 col-md-12 frame"> 
           <div className="col-xs-12 col-md-7"> 
            <h3>Dodaj nową rozmowę</h3> 
            <ConvForm onConvSubmit={this.handleCommentSubmit} /> 
           </div> 
           <div className="col-xs-12 col-md-5"> 
            <ConvList key='1' id='1' data={this.state.records} /> 
           </div> 
          </div> 
         </div> 
       </div> 
       ); 
    } 
}); 

workers.js

/** 
* WorkerList 
* 
*/ 
var Worker = React.createClass({ 
    render: function() { 
     return (
     <div> 
       {this.props.children} 
     </div> 
    ); 
    } 
}); 


/** 
* WorkerList 
* 
*/ 
var WorkerList = React.createClass({ 
    render: function() { 
     return (
     <div className="worker-list"> 
       {this.props.data.map((worker) => 
        <Worker id={worker.id} key={worker.id}> 
         <div className="row"> 
          <div className="col-xs-12 col-md-12 frame"> 
           <div className="col-xs-12 col-md-5"> 
            <h4>{worker.username}</h4> 
           </div> 
           <div className="col-xs-12 col-md-7"> 
           <ConvList key={worker.id} id={worker.id} /> 
           </div> 
          </div> 
         </div> 
        </Worker> 
       )} 

     </div> 

    ); 
    } 
}); 

/** 
* WorkerForm 
* 
*/ 
var WorkerForm = React.createClass({ 
    render: function() { 
    return (
     <div> 
     </div> 
    ); 
    } 
}); 


/** 
* WorkerFame 
* 
*/ 
window.WorkerFrame = React.createClass({ 
    getInitialState: function() { 
     return {data: []}; 
    }, 
    loadWorkersFromServer: function() { 
     $.ajax({ 
      url: baseUrl + '/api/pracownicy', 
      type: 'GET', 
      success: function (data) { 
       this.setState({data: data}); 
      }.bind(this), 
      error: function (xhr, status, err) { 
       console.error(this.props.url, status, err.toString()); 
      }.bind(this) 
     }); 
    }, 
    componentDidMount: function() { 
     this.loadWorkersFromServer(); 
    }, 
    handleWorkerSubmit: function (worker) { 
     $.ajax({ 
      url: baseUrl + '/api/pracownicy', 
      type: 'POST', 
      data: worker, 
      success: function (data) { 
       this.loadWorkersFromServer(); 
      }.bind(this), 
      error: function (xhr, status, err) { 
       console.error(this.props.url, status, err.toString()); 
      }.bind(this) 
     }); 
    }, 
    render: function() { 
     return (
       <div className="add-new"> 
         <div className="row"> 
          <div className="col-xs-12 col-md-12"> 
           <WorkerList data={this.state.data} /> 
          </div> 
          <div className="col-xs-12 col-md-12"> 
           <WorkerForm onWorkerSubmit={this.handleWorkerSubmit} /> 
          </div> 
         </div> 
       </div> 
       ); 
    } 
}); 

final.js

var DashFrame = React.createClass({ 
    render: function() { 
    return (
     <div> 
     <ConvFrame/> 
     <WorkerFrame/> 
     </div> 
    ); 
    } 
}); 


ReactDOM.render(
    <DashFrame/>, 
    document.getElementById('dashboard') 
); 

Irgendwelche Hinweise geschätzt würde. Mein Gehirn kocht.

+1

Haben Sie eine Slogan-Seite dafür? –

Antwort

3

Von dem (lange) Code denke ich, Ihr Problem entsteht, weil Ihr Aufruf zum Server in <ConvList> innerhalb componentDidMount() ist:

componentDidMount: function() { 
    this.loadConvsFromServer(); 
}, 

componentDidMount() wird immer nur einmal aufgerufen, bei der anfänglichen Montage.

Also das zweite Mal, reagieren nicht componentDidMount(), der Anruf zum Server wird nicht gemacht, Ihr Zustand ändert sich nicht, und daher <ConvList> wird nicht aktualisiert.

Um dies zu beheben, fügen:

componentDidUpdate: function() { 
    this.loadConvsFromServer(); 
}, 

Welche auch wenn die Komponente aktualisiert wird, aufgerufen wird.

UPDATE: Sie werden auch eine Bedingung zur resultierenden setState() hinzufügen müssen, sonst werden Sie eine Endlosschleife (componentDidUpdate() ->setState() ->componentDidUpdate() -> Wiederholung).

Der beste Platz ist wahrscheinlich innerhalb loadConvsFromServer(). Etwas wie:

... 
success: function (data) { 
      var dataChanged = ... 
      // some smart comparison of data with this.state.data 
      if (dataChanged) { 
      this.setState({data: data}); 
      } 
     }.bind(this), 
... 

Auch bemerkte ich diesen Schnipsel innen submitForm() innen <ConvForm>:

this.props.onConvSubmit({phone: phone, name: name, number: number, text: text}); 
this.setState({phone: '', text: '', number: '', name: ''}); 

Das ist eine gefährliche Combo: Der erste Anruf geht im Wesentlichen der Steuerung zurück zum übergeordneten, die möglicherweise (und wahrscheinlich will) den gesamten Baum neu rendern. Und sofort danach triggern Sie einen zweiten Render mit der setState(). Aber keine Möglichkeit zu sagen, in welcher Reihenfolge sie gefeuert werden.
Cleaner (und einfacher zu debuggen und zu warten) wäre, die setState() zu verschieben, die alle Eingaben in eine componentWillReceiveProps() Lifecycle-Methode löscht. Auf diese Weise werden jedes Mal, wenn Ihre Komponente vom übergeordneten Element erneut gerendert wird, alle Eingaben gelöscht. Bonus: Ihre Komponente wird nur einmal erneut gerendert.

+0

Danke, aber aus irgendeinem Grund scheint es nicht zu funktionieren. Tatsächlich verursacht das Hinzufügen eines componentDidUpdate eine Endlosschleife von Serverabfragen, immer und immer wieder ... Wenn Sie möchten, kann ich das Projekt zippen und es Ihnen mailen oder es an einen beliebigen Ort hochladen. – rass

+1

Ja, es kann eine Endlosschleife verursachen. Sie müssen eine Bedingung in 'loadConvsFromServer()' hinzufügen, um 'setState()' nur auszuführen, wenn sich die Dinge geändert haben. Wird bearbeiten. – wintvelt

+0

Danke. Du hast mir eine weitere schlaflose Nacht gerettet. – rass

Verwandte Themen