2016-04-20 153 views
4

我對ReactJS很陌生,我的問題很不尋常,可能僅僅是因爲我沒有按照他們的意思來實現。React Js組件在狀態變化後只渲染一次

所以基本上說,這工作得很好,但我需要添加一些新的功能,以及...好吧,一些東西關閉。

首先 - ConvFrame是頂級組件,在頁面的頂部出現,它由ConvForm組件(添加新的查詢)ConvList,其中未分配的和新的呼叫去的。 ConvList此處的ID和密鑰爲1.

下面還有工作人員列表,他們只使用ConvForm,這些字段本身是dropzones,允許快速分配新的呼叫任務。這裏的ConvList有Id和Key等於worker的id。

ConvList被呈現時,它查詢服務器列表中的作業。這對所有人都適用。但是,當通過ConvForm添加新項目時,似乎出現了一些奇怪的問題。它調用handleCommentSubmit()功能,來電this.loadCommentsFromServer();(愚蠢的,我知道!),然後設置爲記錄this.setState({records: data});

當第一個記錄添加了/api/zlecenia被調用兩次新的狀態。一旦從loadCommentsFromServer()裏面ConvFrame和第二次從ConvList內。通過表單添加第二條記錄調用一次,組件似乎不會對狀態變化做出反應。我猜,有些東西實施得不好。

這裏的源代碼: 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') 
); 

,將不勝感激任何提示。我的大腦正在沸騰。

+1

你有這樣一個plunker頁面嗎? –

回答

3

從(長)的代碼,我覺得你的問題是因爲在<ConvList>您的服務器調用內部componentDidMount()

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

componentDidMount()總是隻調用一次,在初始安裝。

所以第二次,反應不會叫componentDidMount(),調用服務器是不會發生的,您的狀態不會改變,因此<ConvList>不會更新。

要修復,添加:

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

當組件的更新也稱爲。

UPDATE:你還需要一個條件添加到產生setState(),否則你將得到一個死循環(componentDidUpdate() - >setState() - >componentDidUpdate() - >重複)。

最好的地方可能在loadConvsFromServer()之內。喜歡的東西:

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

而且,我注意到這個片段裏submitForm()<ConvForm>

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

這是一個危險的組合:第一個電話基本通控制權交還給父母,這可能(也可能將會)重新渲染整個樹。之後,您立即用setState()觸發第二次渲染。但沒有辦法告訴他們將被解僱的順序。
清理器(並且更易於調試和維護)將移動將所有輸入清除爲componentWillReceiveProps()生命週期方法的setState()。這樣,每次您的組件被父級重新渲染時,所有輸入都會清除。獎勵:你的組件只會重新渲染一次。

+0

謝謝,但由於某種原因,它似乎不起作用。實際上,添加componentDidUpdate會導致無限循環的服務器查詢,如果您希望我可以壓縮項目並將其郵寄給您或上傳到某個地方。 – rass

+1

是的,它會導致無限循環。你需要在'loadConvsFromServer()'中添加一些條件來只在setState()方法中改變。將編輯。 – wintvelt

+0

謝謝。你救了我又一個不眠之夜。 – rass