admin管理员组文章数量:1302869
I'm quite new to ReactJS and my problems is quite unusual, may be occurring simply because I don't implement things the way they are meant to be.
So basically speaking, this was working fine before, but I needed to add some new features, and... well, something's off.
To start with - ConvFrame
is top ponent, appearing at the top on the page and it consists of ConvForm
ponent (to add new queries) and ConvList
where unassigned and new calls go. The ConvList
here has ID and Key of 1.
There is also list of workers below, they are using ConvForm
only, the fields themselves are dropzones, which allow to assign new call tasks quickly. The ConvList
here have Id and Key equal to id of worker.
As the ConvList
is rendered it queries server for jobs within the list. And this works just fine for all of them. However, there seems to be some weird problem when new item is added via ConvForm
. It calls handleCommentSubmit()
function, calls this.loadCommentsFromServer();
(stupid, I know!) and then sets new state for records this.setState({records: data});
When first record is added the /api/zlecenia
is called twice. Once from the loadCommentsFromServer()
inside ConvFrame
and second time from within ConvList
. Adding second record via form calls it once, the ponent seem to not react to the state change. Something is badly implemented , I guess.
Here's the source code: 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 ponent, 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 ponent 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)
});
},
ponentDidMount: 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)
});
},
ponentDidMount: 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')
);
Any hints would be appreciated. My brain is boiling.
I'm quite new to ReactJS and my problems is quite unusual, may be occurring simply because I don't implement things the way they are meant to be.
So basically speaking, this was working fine before, but I needed to add some new features, and... well, something's off.
To start with - ConvFrame
is top ponent, appearing at the top on the page and it consists of ConvForm
ponent (to add new queries) and ConvList
where unassigned and new calls go. The ConvList
here has ID and Key of 1.
There is also list of workers below, they are using ConvForm
only, the fields themselves are dropzones, which allow to assign new call tasks quickly. The ConvList
here have Id and Key equal to id of worker.
As the ConvList
is rendered it queries server for jobs within the list. And this works just fine for all of them. However, there seems to be some weird problem when new item is added via ConvForm
. It calls handleCommentSubmit()
function, calls this.loadCommentsFromServer();
(stupid, I know!) and then sets new state for records this.setState({records: data});
When first record is added the /api/zlecenia
is called twice. Once from the loadCommentsFromServer()
inside ConvFrame
and second time from within ConvList
. Adding second record via form calls it once, the ponent seem to not react to the state change. Something is badly implemented , I guess.
Here's the source code: 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 ponent, 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 ponent 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)
});
},
ponentDidMount: 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)
});
},
ponentDidMount: 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')
);
Any hints would be appreciated. My brain is boiling.
Share Improve this question edited Apr 21, 2016 at 6:34 rass asked Apr 20, 2016 at 18:26 rassrass 1071 gold badge1 silver badge11 bronze badges 1- 1 Do you have a plunker page for this? – Fernando Chavez Herrera Commented Apr 20, 2016 at 18:31
1 Answer
Reset to default 5From the (long) code I think your problem arises because your call to server in <ConvList>
is inside ponentDidMount()
:
ponentDidMount: function () {
this.loadConvsFromServer();
},
ponentDidMount()
is always only called once, upon initial mount.
So the second time, react does not call ponentDidMount()
, the call to server is not made, your state does not change, and therefore <ConvList>
is not updated.
To fix, add:
ponentDidUpdate: function () {
this.loadConvsFromServer();
},
Which is also called when ponent is updated.
UPDATE: you will also need to add a condition to the resulting setState()
, otherwise you will get an endless loop (ponentDidUpdate()
-> setState()
-> ponentDidUpdate()
-> repeat).
Best place is probably inside loadConvsFromServer()
. Something like:
...
success: function (data) {
var dataChanged = ...
// some smart parison of data with this.state.data
if (dataChanged) {
this.setState({data: data});
}
}.bind(this),
...
Also, I noticed this snippet inside submitForm()
inside <ConvForm>
:
this.props.onConvSubmit({phone: phone, name: name, number: number, text: text});
this.setState({phone: '', text: '', number: '', name: ''});
This is a dangerous bo: the first call essentially passes control back to the parent, which may (and probably will) re-render the entire tree. And immediately after, you trigger a second render with the setState()
. But no way of telling in which order they will be fired.
Cleaner (and easier to debug and maintain) would be to move the setState()
that clears all inputs to a ponentWillReceiveProps()
lifecycle method. That way, each time your ponent is re-rendered by its parent, all inputs will clear. Bonus: your ponent will only re-render once.
本文标签: javascriptReact Js component rendering only once after state changeStack Overflow
版权声明:本文标题:javascript - React Js component rendering only once after state change - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1741736560a2395086.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论