React - how can I get updated data to show in my table? - reactjs

I need to be able to update the values in my table rows and then have those new values show in the cells. How would I go about doing this?
Here is the code I am currently working with:
Main Table Component
import React from 'react';
import TableWithDataHeader from './TableWithDataHeader.jsx';
import TableWithDataBody from './TableWithDataBody.jsx';
import TableWithDataRowForm from './TableWithDataRowForm.jsx';
import {updateRowHistory} from '../../actions/DALIActions';
import AppStore from '../../stores/AppStore';
export default class TableWithData extends React.Component {
state = {rows: [], isEditing: false, input: null};
updateState = () => {
let rows = this.state.rows;
rows.shift();
rows.push({id: AppStore.getRowId(), cells: AppStore.getUpdatedCells()});
this.setState({rows});
console.log(rows);
};
componentDidMount() {
let rows = this.state.rows;
rows.push({id: AppStore.getRowId(), cells: AppStore.getCells().historycells});
this.setState({rows});
console.log(rows);
AppStore.addChangeListener(this.updateState);
}
handleEdit = (row) => {
this.setState({isEditing: true});
};
handleInputChange = (newCellValuesArray) => {
let input = this.state.input;
input = newCellValuesArray;
this.setState({input});
};
editStop = (row) => {
this.setState({isEditing: false});
};
handleSubmit = (access_token, row_id) => {
let newCellValuesArray = this.state.input;
updateRowHistory(access_token, row_id, newCellValuesArray);
this.setState({isEditing: false});
};
componentWillUnmount() {
AppStore.removeChangeListener(this.updateState);
}
render() {
let {rows, isEditing, input} = this.state;
console.log(rows);
console.log(rows.map(row => {
return row.cells;
}));
return (
<div>
<div className="row">
<table className="table table-striped">
<thead>
<TableWithDataHeader />
</thead>
<tbody>
{rows.map(row => this.state.isEditing ?
<TableWithDataRowForm key={row.id} cells={row.cells} editStop={this.editStop.bind(null, row)} handleSubmit={this.handleSubmit.bind(this)} handleInputChange={this.handleInputChange.bind(this)} /> :
<TableWithDataBody key={row.id} cells={row.cells} handleEdit={this.handleEdit.bind(null, row)} />
)}
</tbody>
</table>
</div>
</div>
);
}
}
Edit Row Component
import React from 'react';
import AppStore from '../../stores/AppStore';
export default class TableWithDataRowForm extends React.Component {
state = {cells: this.props.cells, newCellValues: []};
onChange(e) {
let newCellValues = this.state.newCellValues;
newCellValues[e.target.id] = e.target.value;
this.setState({newCellValues});
console.log(newCellValues);
let newCellValuesArray = [];
for (let key in newCellValues) {
if (newCellValues.hasOwnProperty(key)) {
newCellValuesArray.push({contents: newCellValues[key]});
}
}
console.log(newCellValuesArray);
this.props.handleInputChange(newCellValuesArray);
}
editStop() {
this.props.editStop();
}
handleSubmit(e) {
e.preventDefault();
let access_token = AppStore.getToken();
let row_id = AppStore.getRowId();
this.props.handleSubmit(access_token, row_id);
}
render() {
let {cells, newCellValues} = this.state;
return (
<tr>
{cells.map(cell => {
return <td key={cell.id} className="text-center"><input type="text" className="form-control" id={cell.id} defaultValue={cell.contents} onChange={this.onChange.bind(this)} /></td>
})}
<td>
<button className="btn btn-default"><i className="fa fa-ban" onClick={this.editStop.bind(this)}></i>Cancel</button>
<button className="btn btn-success"><i className="fa fa-cloud" onClick={this.handleSubmit.bind(this)}></i>Save</button>
</td>
</tr>
);
}
}
It's it bit mangled at the moment, but I think that you can get the general idea of what I am attempting! So, I can get the table to initially render with data values from my store and I can successfully edit them to different values. However, I would like it so that when I click my save button the new values show. I am using React with flux to build this.
Answers with examples are always much appreciated
Thanks for your time

Your problem is that you have the state of our cells twice.
Once in your row and once in your table.
You should never do this but have the state only in the table and pass them as prop and access them as prop. Only the temporary edited vakue should be saved as an extra state.
You can get the prop changes via componentWillReceiveProps.
Here an stripped down example:
var Row = React.createClass({
getInitialState: function() {
return {
tempValue: this.props.value
}
},
componentWillReceiveProps: function(nextProps) {
//here u might want to check if u are currently editing but u get the idea -- maybe u want to reset it to the current prop on some cancelEdit method instead
this.setState({
tempValue: nextProps.value
});
},
render: function() {
return <div><input type="text" value={this.state.tempValue} onChange={this.onChange} /></div>;
},
onChange: function(e) {
this.setState({
tempValue: e.target.value
});
}
});
var Hello = React.createClass({
getInitialState: function() {
return {
value: 'someServerState'
}
},
render: function() {
return (
<div>
<Row value={this.state.value} />
<button onClick={this.reloadFromServer} >reload from Server</button>
</div>
);
},
//this will be triggered by some of ur events - i added a button
reloadFromServer: function() {
this.setState({
value: 'someServerState changed somehow'
});
}
});
see: https://jsfiddle.net/69z2wepo/34292/

Related

How to delete an element in list in react?

i am trying to do a simple toDo app with react. I couldnt do how to delete an element in list. Here my code; first state:
class AppForm extends Component {
constructor(props) {
super(props);
this.state = { items: [] , text:''};
this.onChangeHandler=this.onChangeHandler.bind(this)
this.submitHandler=this.submitHandler.bind(this)
}
//setting input value to the text in state
onChangeHandler = (e) => {
this.setState({
text: e.target.value
});
};
//pushing text item of the state to the items
submitHandler = (e) => {
e.preventDefault();
const arrayItem = {
text: this.state.text,
};
this.setState(state => ({
items: state.items.concat(arrayItem),
text: ''
}));
}
here the problem area. I also tried splice but couldnt.
deleteItem=(index)=>{
let todos= this.state.items.filter(todo => index !== todo.key)
this.setState({
items : todos
})
}
then rendering..
render() {
return (
<div>
<h1>toDo App</h1>
<form onSubmit={this.submitHandler}>
<label>Type the task you want to do!</label>
<input type="text" onChange={this.onChangeHandler} value={this.state.text}/>
</form>
<ul>
{this.state.items.map((item,index) =>{
return (
<li key={index}> {item.text}
<p onClick={this.deleteItem.bind(this,index)}> X </p>
</li>
)
})}
</ul>
</div>
);
}
}
export default AppForm;
Splice is the answer.
First, I create a copy of your state array. Then splice it using the index clicked. Then set setState with the spliced array.
deleteItem=(index)=>{
let todos= [...this.state.items]
todos.splice(index, 1)
this.setState({
items : todos
})
}
deleteItem = (index) => {
this.setState(({items}) => {
return {items: [...items.filter(todo => index !== todo.key)]};
})
}
First of all you're not setting the key anywhere when you are inserting in array. It is not at all recommended to use index as key in array. It should be unique.
const arrayItem = {
text: this.state.text,
id: uuid()
};
So I've added the uuid and compared with the id of the element.
codesandbox
uuid
// UNIQUE KEY GENERATOR
function uuidv4() {
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
var r = (Math.random() * 16) | 0,
v = c == "x" ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
}
export default uuidv4;
React component
import React, { Component } from "react";
import uuid from "./uuid";
import "./styles.css";
class App extends Component {
constructor(props) {
super(props);
this.state = { items: [], text: "" };
this.onChangeHandler = this.onChangeHandler.bind(this);
this.submitHandler = this.submitHandler.bind(this);
}
//setting input value to the text in state
onChangeHandler = (e) => {
this.setState({
text: e.target.value
});
};
//pushing text item of the state to the items
submitHandler = (e) => {
e.preventDefault();
const arrayItem = {
text: this.state.text,
id: uuid()
};
this.setState((state) => ({
items: state.items.concat(arrayItem),
text: ""
}));
};
deleteItem = (key) => {
let todos = this.state.items.filter((todo) => key !== todo.id);
this.setState({
items: todos
});
};
render() {
return (
<div>
<h1>toDo App</h1>
<form onSubmit={this.submitHandler}>
<label>Type the task you want to do!</label>
<input
type="text"
onChange={this.onChangeHandler}
value={this.state.text}
/>
</form>
<ul>
{this.state.items.map((item) => {
return (
<li key={item.id}>
{item.text}
<p onClick={() => this.deleteItem(item.id)}> X </p>
</li>
);
})}
</ul>
</div>
);
}
}
export default App;

Delete record from todo list in ReactJS giving error

Am learning ReactJS and building my todo application.
However am facing an issue when I try to delete a task.
I have two files TodoList.js and TodoItems.js
TodoList.js
import React, {Component} from 'react';
import TodoItems from './TodoItems';
class TodoList extends Component {
//Function to handle adding tasks
addTask(event) {
//Get task Value
let task = this.refs.name.value;
//Newitem Object
if (task !== "") {
let newItem = {
text: task,
key: Date.now()
}
this.setState({
items: this.state.items.concat(newItem)
});
this.refs.name.value = ""; //Blank out the task input box
}
}
deleteItem(key) {
var filteredItems = this.state.items.filter(function (item) {
return (item.key !== key);
});
this.setState({
items: filteredItems
});
}
constructor(props) {
super(props);
this.state = {
items: []
};
this.addTask = this.addTask.bind(this);
this.deleteItem = this.deleteItem.bind(this);
}
render() {
return (
<div className="todoListMain">
<div className="header">
<form>
<input placeholder="Enter Task" id="name" ref="name"></input>
<button type="button" onClick={this.addTask}>Add Task</button>
</form>
</div>
<div className="list">
<TodoItems entries={this.state.items} delete={this.deleteItem} />
</div>
</div>
);
}
}
export default TodoList;
TodoItems.js has following code
import React, {Component} from 'react';
class TodoItems extends Component {
constructor(props) {
super(props);
this.state = {};
}
delete(key) {
this.props.delete(key);
}
listTasks(item) {
return <li key={item.key} onClick={() => this.delete(item.key)}>{item.text}</li>
}
render() {
let entries = this.props.entries;
let listItems = entries.map(this.listTasks);
return (
<ul className="theList">
{listItems}
</ul>
);
}
}
export default TodoItems;
I am getting an error on deleting task when clicked on it.
and I am getting error as here
I guess it means function delete is not defined but it has been defined still am getting an error.
Can anyone explain how do I resolve this issue?
You should never attempt to modify your props directly, if something in your components affects how it is rendered, put it in your state :
this.state = {
entries: props.entries
};
To delete your element, just filter it out of your entries array :
delete(key) {
this.setState(prevState => ({
entries: prevState.entries.filter(item => item.key !== key)
}))
}
And now the render function :
render() {
const { entries } = this.state //Takes the entries out of your state
return (
<ul className="theList">
{entries.map(item => <li key={item.key} onClick={this.delete(item.key)}>{item.text}</li>)}
</ul>
);
}
Full code :
class TodoItems extends Component {
constructor(props) {
super(props);
this.state = {
entries: props.entries
};
}
delete = key => ev => {
this.setState(prevState => ({
entries: prevState.entries.filter(item => item.key !== key)
}))
}
render() {
const { entries } = this.state
return (
<ul className="theList">
{entries.map(item => <li key={item.key} onClick={this.delete(item.key)}>{item.text}</li>)}
</ul>
);
}
}
You should also try to never use var. If you do not plan to modify a variable, use const, otherwise, use let.
EDIT : The error shown in your edit come from listTasks not being bound to your class. To solve it you can either bind it (as shown in an other answer below) or convert it in another function :
listTasks = item => {
return <li key={item.key} onClick={() => this.delete(item.key)}>{item.text}</li>
}
Short syntax :
listTasks = ({ key, text }) => <li key={key} onClick={() => this.delete(key)}>{text}</li>
Welcome to Stackoverflow!
Check this section of the React Docs. You either have to bind your class functions in the constructor or use arrow functions.
class TodoItems extends Component {
constructor(props) {
// ...
this.delete = this.delete.bind(this);
}
delete(key) {
this.props.delete(key);
}
// Or without binding explicitly:
delete2 = (key) => {
// ...
}
}
Replace this:
onClick={this.delete(item.key)}
// passes the result of `this.delete(item.key)` as the callback
By this:
onClick={() => this.delete(item.key)}
// triggers `this.delete(item.key)` upon click

Reactjs setState not updating for this one function only

For this application, clicking a listed item once should create a button component underneath this listed item. Clicking the button should cause this listed item to be deleted.
I am currently facing difficulty trying to 'delete' the listed item after the button is clicked. Here is the code that went wrong (this is found in CountdownApp component) :
handleDelete(index) {
console.log('in handleDelete')
console.log(index)
let countdownList = this.state.countdowns.slice()
countdownList.splice(index, 1)
console.log(countdownList) // countdownList array is correct
this.setState({
countdowns: countdownList
}, function() {
console.log('after setState')
console.log(this.state.countdowns) // this.state.countdowns does not match countdownList
console.log(countdownList) // countdownList array is still correct
})
}
In the code above, I removed the item to be deleted from countdownList array with splice and tried to re-render the app with setState. However, the new state countdowns do not reflect this change. In fact, it returns the unedited state.
I have also tried the following:
handleDelete(index) {
this.setState({
countdowns: [] // just using an empty array to check if setState still works
}, function() {
console.log('after setState')
console.log(this.state.countdowns)
})
}
In the code above, I tried setting state to be an empty array. The console log for this.state.countdowns did not print out an empty array. It printed out the unedited state again
This is the only event handler that isn't working and I have no idea why (main question of this post) :/
If I have 'setstate' wrongly, why does the other 'setState' in other parts of my code work?? (I would like to request an in-depth explanation)
This is all my code for this app (its a small app) below:
import React from 'react'
import ReactDOM from 'react-dom'
class DeleteButton extends React.Component {
render() {
return (
<ul>
<button onClick={this.props.onDelete}>
delete
</button>
</ul>
)
}
}
class Countdown extends React.Component {
render () {
//console.log(this.props)
return (
<li
onClick={this.props.onClick}
onDoubleClick={this.props.onDoubleClick}
>
{this.props.title} - {this.props.days}, {this.props.color}
{this.props.showDeleteButton ? <DeleteButton onDelete={this.props.onDelete}/> : null }
</li>
)
}
}
const calculateOffset = date => {
let countdown = new Date(date)
let today = new Date
let timeDiff = countdown.getTime() - today.getTime()
let diffDays = Math.ceil(timeDiff / (1000 * 3600 * 24))
return diffDays
}
class CountdownList extends React.Component {
countdowns() {
let props = this.props
// let onClick = this.props.onClick
// let onDoubleClick = this.props.onDoubleClick
let rows = []
this.props.countdowns.forEach(function(countdown, index) {
rows.push(
<Countdown
key={index}
title={countdown.title}
days={calculateOffset(countdown.date)}
color={countdown.color}
showDeleteButton={countdown.showDeleteButton}
onDelete={() => props.onDelete(index)}
onClick={() => props.onClick(index)}
onDoubleClick={() => props.onDoubleClick(index)}
/>
)
})
return rows
}
render() {
return (
<div>
<ul>
{this.countdowns()}
</ul>
</div>
)
}
}
class InputField extends React.Component {
render() {
return (
<input
type='text'
placeholder={this.props.placeholder}
value={this.props.input}
onChange={this.props.handleInput}
/>
)
}
}
class DatePicker extends React.Component {
render() {
return (
<input
type='date'
value={this.props.date}
onChange={this.props.handleDateInput}
/>
)
}
}
class CountdownForm extends React.Component {
constructor(props) {
super(props)
this.state = {
title: this.props.title || '',
date: this.props.date || '',
color: this.props.color || ''
}
}
componentWillReceiveProps(nextProps) {
this.setState({
title: nextProps.title || '',
date: nextProps.date || '',
color: nextProps.color || ''
})
}
handleSubmit(e) {
e.preventDefault()
this.props.onSubmit(this.state, this.reset())
}
reset() {
this.setState({
title: '',
date: '',
color: ''
})
}
handleTitleInput(e) {
this.setState({
title: e.target.value
})
}
handleDateInput(e) {
this.setState({
date: e.target.value
})
}
handleColorInput(e) {
this.setState({
color: e.target.value
})
}
render() {
return (
<form
onSubmit={(e) => this.handleSubmit(e)}
>
<h3>Countdown </h3>
<InputField
placeholder='title'
input={this.state.title}
handleInput={(e) => this.handleTitleInput(e)}
/>
<DatePicker
date={this.state.date}
handleDateInput={(e) => this.handleDateInput(e)}
/>
<InputField
placeholder='color'
input={this.state.color}
handleInput={(e) => this.handleColorInput(e)}
/>
<button type='submit'>Submit</button>
</form>
)
}
}
class CountdownApp extends React.Component {
constructor() {
super()
this.state = {
countdowns: [
{title: 'My Birthday', date: '2017-07-25', color: '#cddc39', showDeleteButton: false},
{title: 'Driving Practice', date: '2017-07-29', color: '#8bc34a', showDeleteButton: false},
{title: 'Korean BBQ', date: '2017-08-15', color: '#8bc34a', showDeleteButton: false}
]
}
}
handleCountdownForm(data) {
if (this.state.editId) {
const index = this.state.editId
let countdowns = this.state.countdowns.slice()
countdowns[index] = data
this.setState({
title: '',
date: '',
color: '',
editId: null,
countdowns
})
} else {
data.showDeleteButton = false
const history = this.state.countdowns.slice()
this.setState({
countdowns: history.concat(data),
})
}
}
handleDelete(index) {
console.log('in handleDelete')
console.log(index)
let countdownList = this.state.countdowns.slice()
countdownList.splice(index, 1)
console.log(countdownList)
this.setState({
countdowns: countdownList
}, function() {
console.log('after setState')
console.log(this.state.countdowns)
})
}
handleCountdown(index) {
const countdownList = this.state.countdowns.slice()
let countdown = countdownList[index]
countdown.showDeleteButton = !countdown.showDeleteButton
this.setState({
countdowns: countdownList
})
}
handleDblClick(index) {
const countdownList = this.state.countdowns
const countdown = countdownList[index]
this.setState({
title: countdown.title,
date: countdown.date,
color: countdown.color,
editId: index
})
}
render() {
return (
<div>
<CountdownForm
title={this.state.title}
date={this.state.date}
color={this.state.color}
onSubmit={(data) => {this.handleCountdownForm(data)}}
/>
<CountdownList
countdowns={this.state.countdowns}
onDelete={(index) => this.handleDelete(index)}
onClick={(index) => this.handleCountdown(index)}
onDoubleClick={(index) => this.handleDblClick(index)}
/>
</div>
)
}
}
ReactDOM.render(
<CountdownApp />,
document.getElementById('app')
)
I managed to find the answer to my own question!
setState worked as expected. The bug was due to <li> container that wrapped the event handler.
Clicking <li> causes it to call onClick event (which is managed by handleCountdown function in CountdownApp component) which causes it to setState.
As the delete button was wrapped in <li> container, clicking the delete button calls 2 event listeners - handleCountdown and handleDelete. handleCountdown is called twice in this case, once from clicking <li> to expand and the next call when the delete button is clicked.
There is a high chance that the last async setState dispatched from handleCountdown overwrites handleDelete's setState. Hence, the bug.
Here is changes: (I recoded everything again so the names might differ a little but the logic stays the same)
class Countdown extends React.Component {
render () {
return (
<li>
<div onClick={this.props.onClick} > // Add this div wrapper!
{this.props.title} - {this.props.days}, {this.props.color}
</div>
{this.props.toShow ?
<ButtonsGroup
onDelete={this.props.onDelete}
onEdit={this.props.onEdit}
/>
: null}
</li>
)
}
}
So the solution is to separate the clickable area and the buttons. I added a div wrapper over the text in <li> so whenever the text in <li> is clicked, the added <ul> will be out of onClick event handler area.

Avoid navigation in react.js on button click

I am very new to react.js and trying to create a react component to render a json response from a REST API created in Python-Flask on button click.
Everything works fine but I am being navigated to the same page again and output which is a table does not persist.
We can see the console shows navigation back to the same page, which resets the component's state.
Snapshot of the console output shows the behavior
My component code:
var cols = [
{ key: 'id', label: 'Id' },
{ key: 'owner', label: 'Owner' },
{ key: 'path', label: 'Path' },
{ key: 'description', label: 'Description' }
];
class SearchForm extends React.Component {
constructor(props) {
super(props);
this.state = {items: [], searchString: ''};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({searchString: event.target.value});
console.log(this.state.items);
}
handleSubmit(event) {
// alert('A name was submitted: ' + this.state.searchString);
// event.preventDefault();
// this.getMoviesFromApiAsync();
console.log(this.state.searchString);
this.getData();
}
getData() {
// Todo: Append the searchstring to the URI
fetch("http://localhost:5000/search")
.then(response => response.json())
.then(json => {
console.log("Inside request: ");
console.log(json.Data);
this.setState({
items: json.Data
});
console.log("after copy to state");
console.log(this.state.items);
});
}
generateRows() {
var cols = this.props.cols, // [{key, label}]
data = this.state.items;
console.log("Inside functions");
console.log(data);
// console.log(data);
return data.map(function(item) {
// handle the column data within each row
var cells = cols.map(function(colData) {
// colData.key might be "firstName"
return <td key={colData.key}> {item[colData.key]} </td>;
});
return <tr key={item.id}> {cells} </tr>;
});
}
generateHeaders() {
var cols = this.props.cols; // [{key, label}]
// generate our header (th) cell components
return cols.map(function(colData) {
return <th key={colData.key}> {colData.label} </th>;
});
}
render() {
var headerComponents = this.generateHeaders(),
rowComponents = this.generateRows();
return (
<div>
<form onSubmit={this.handleSubmit.bind(this)}>
<input type="text" value={this.state.searchString} onChange={this.handleChange.bind(this)} />
<input type="submit" value="Search" />
</form>
<br />
<div class="table-responsive">
<table class="table">
<thead> {headerComponents} </thead>
<tbody> {rowComponents} </tbody>
</table>
</div>
</div>
);
}
}
module.exports = SearchForm;
const main = document.getElementById('main');
ReactDOM.render(<SearchForm cols={cols}/>, main);
Formatting this as an answer for future reference. When using a form, the event handler for form submission needs to have
event.preventDefault()
in order to keep the default form submission and redirect from happening.
Reference

React Drag n Drop PUT request after props

I am building an application that requires a table of items to be sorted and change the orderNumber of them depending on their sorting. I installed and utilized a library called react-dnd to handle the functionality of sorting/ordering, and its working great so far. The issue im having is the update. When a user moves one of the items, I need to send a PUT request to the api and update its orderNumber. It was working last night great, here is my code.
The ListItem (Item that is being sorted and updated):
import React, {PropTypes} from 'react';
import {Link} from 'react-router';
import {DragSource, DropTarget} from 'react-dnd';
import sdk from '../../js/sdk';
import ItemTypes from './ItemTypes';
const itemSource = {
beginDrag(props) {
return {id: props.id};
}
};
const itemTarget = {
hover(props, monitor) {
const draggedId = monitor.getItem().id;
if (draggedId !== props.id) {
props.swapItems(draggedId, props.id);
}
}
};
const DragSourceDecorator = DragSource(ItemTypes.ITEM, itemSource, (connect, monitor) => {
return {
connectDragSource: connect.dragSource(),
isDragging: monitor.isDragging(),
};
});
const DropTargetDecorator = DropTarget(ItemTypes.ITEM, itemTarget, (connect) => {
return {connectDropTarget: connect.dropTarget()};
});
class SwagBagItem extends React.Component {
constructor(props) {
super(props);
this._handleDelete = this._handleDelete.bind(this);
}
componentWillReceiveProps(nextProps) {
const swagbagItemCpy = Object.assign({}, nextProps.swagbagItem);
delete swagbagItemCpy.id;
if (nextProps) {
sdk.put(`swagbags/${nextProps.swagbag.id}/items/${nextProps.swagbagItem.id}`, swagbagItemCpy)
.done((result) => {
console.log(result);
}).fail((error) => {
console.log(error);
})
;
}
}
_handleDelete(event) {
event.preventDefault();
event.stopPropagation();
if (confirm('Are you sure you want to delete this Swagbag Item?')) {
sdk.delete(`swagbags/${this.props.swagbag.id}/items/${this.props.swagbagItem.id}`)
.done(() => {
console.log('Swagbag Item remove!');
}).then(() => {
this.props.loadSwagBags();
});
}
}
render() {
const {swagbagItem} = this.props;
return this.props.connectDragSource(this.props.connectDropTarget(
<tr className="swagbag-item">
<td>{swagbagItem.id}</td>
<td><Link to={`${this.props.swagbag.id}/items/${swagbagItem.id}`}>{swagbagItem.name}</Link></td>
<td>{swagbagItem.uri}</td>
<td>
<div className="btn-group btn-group-xs pull-right" role="group">
<Link to={`${this.props.swagbag.id}/items/${swagbagItem.id}/edit`} className="btn btn-info">Edit</Link>
<Link to={`${this.props.swagbag.id}/items/${swagbagItem.id}`} className="btn btn-info">View</Link>
<button className="btn btn-danger btn-xs" onClick={this._handleDelete}>Remove</button>
</div>
</td>
</tr>
));
}
}
SwagBagItem.propTypes = {
loadSwagBags: PropTypes.func,
params: PropTypes.object,
swagbag: PropTypes.object,
swagbagItem: PropTypes.object,
};
export default DropTargetDecorator(DragSourceDecorator(SwagBagItem));
The container or list that holds these items:
import React, {PropTypes} from 'react';
import {Link} from 'react-router';
import {DragDropContext} from 'react-dnd';
import HTML5Backend from 'react-dnd-html5-backend';
import sdk from '../../js/sdk';
import Nav from '../Nav';
import SwagBagItem from '../SwagBagItem';
class SwagBagItemsList extends React.Component {
constructor(props) {
super(props);
this.state = {
swagbag: null,
swagbagItems: [],
};
this._loadSwagBags = this._loadSwagBags.bind(this);
this._compareItems = this._compareItems.bind(this);
this._swapItems = this._swapItems.bind(this);
}
componentWillMount() {
this._loadSwagBags();
}
_compareItems(item1, item2) {
return item1.orderNumber - item2.orderNumber;
}
_swapItems(itemNo1, itemNo2) {
const items = this.state.swagbagItems;
let item1 = items.filter(item => item.id === itemNo1)[0];
let item2 = items.filter(item => item.id === itemNo2)[0];
let item1Order = item1.orderNumber;
item1.orderNumber = item2.orderNumber;
item2.orderNumber = item1Order;
items.sort(this._compareItems);
this.setState({swagbagItems: items});
}
_loadSwagBags() {
sdk.getJSON(`swagbags/${this.props.params.id}`)
.done((result) => {
this.setState({swagbag: result});
})
.then(() => {
sdk.getJSON(`swagbags/${this.props.params.id}/items?fields=id,name,summary,uri,itemImageFile,orderNumber`).done((results) => {
this.setState({swagbagItems: results});
});
});
}
render() {
let swagbagItems = null;
if (this.state.swagbagItems) {
swagbagItems = this.state.swagbagItems.map((item) => {
return <SwagBagItem
loadSwagBags={this._loadSwagBags}
swagbag={this.state.swagbag}
swagbagItem={item}
key={item.id}
id={item.id}
swapItems={this._swapItems}
/>;
});
}
if (!this.state.swagbag) {
return <div>Loading...</div>;
}
return (
<div>
<h1>Swagbag Items</h1>
<Nav swagbag={this.state.swagbag} />
<table className="table">
<thead>
<tr>
<th>id</th>
<th>name</th>
<th>uri</th>
<th></th>
</tr>
</thead>
<tbody>
{swagbagItems}
</tbody>
</table>
<Link to={`swagbags/createItem/swagbagid/${this.state.swagbag.id}`} className="btn btn-success">Add Item</Link>
</div>
);
}
}
SwagBagItemsList.propTypes = {
params: PropTypes.object,
};
export default DragDropContext(HTML5Backend)(SwagBagItemsList);
It is making the PUT request, but its making hundreds of them in a row from just moving one object. I cant for the life of me figure out why. This puts a severe lag on the application and makes it unresponsive. Am I going about this the right way, and if so, what is the solution to this?
EDIT #1: Woke up today and the application is working fine. Unfortunately this is going in production, so before that I have to recreate the bug of 800+ PUT requests and figure it out. Might put a bounty on this.
If you want to get it so that it sends the update once it's finished dragging, there's an endDrag function you can add to your DragSource (http://gaearon.github.io/react-dnd/docs-drag-source.html) that will only be fired once and will only be fired upon finishing the drag. So if you remove your api call from componentWillReceiveProps and move it to the source like this:
const itemSource = {
beginDrag(props) {
return {
id: props.id,
swagbagId: props.swagbag.id,
swagbagItem: props.swagbagItem,
};
},
endDrag(props, monitor) {
const item = monitor.getItem();
sdk.put(`swagbags/${item.swagbagId}/items/${item.swagbagItem.id}`, item.swagbagItem)
.done((result) => {
console.log(result);
}).fail((error) => {
console.log(error);
})
;
},
};
It should only make the call one time (I can't perfectly predict that without knowing what's in swagbag and swagbagItem but I think it should). Note that I'm using the getItem() function from the DragSource monitor (http://gaearon.github.io/react-dnd/docs-drag-source-monitor.html) to retrieve what was passed in upon beginDrag.

Resources