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
Add a ment  | 

1 Answer 1

Reset to default 5

From 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