2016-04-20 155 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