Reputation: 1844
I have a table with rows and each cell within a row can be edited. There is a button associated with each row that will submit all the cells in that row to a database. What I would like to know is how I can access the new values, if the data in the cell has been changed, from the component that the submit event is actually occurring on?
Here is how I am building the table at the moment:
Main Table:
import React from 'react';
import TableWithDataHeader from './TableWithDataHeader.jsx';
import Row from './Row.jsx';
import AppStore from '../../stores/AppStore';
export default class Table extends React.Component {
handleSubmitEvent = (e) => {
e.preventDefault();
/////What do I do here to get the updated values???////
};
render() {
return (
<div>
<div className="row">
<table className="table table-striped table-bordered">
<thead>
<TableWithDataHeader />
</thead>
<Row data={AppStore.getCells().historycells} handleSubmitEvent={this.handleSubmitEvent} />
</table>
</div>
</div>
);
}
}
Row Component:
import React from 'react';
import TableBody from './TableBody.jsx';
import AppStore from '../../stores/AppStore';
export default class Row extends React.Component {
state = {data: this.props.data};
handleSubmitEvent = (e) => {
this.props.handleSubmitEvent(e);
};
render() {
let {data} = this.state;
return (
<tbody>
<tr id={AppStore.getRowId()}>
{this.state.data.map(cell => {
return (
<TableBody key={cell.id} cell={cell.contents} />
);
})}
<td className="table-button">
<button type="submit" className="btn btn-primary" onClick={this.handleSubmitEvent.bind(this)}>Save Row</button>
</td>
</tr>
</tbody>
);
}
}
Cell Component:
import React from 'react';
import EditableCells from './EditableCells.jsx';
export default class TableBody extends React.Component {
render() {
return (
<EditableCells data={this.props.cell} />
);
}
}
Edit Cell Component:
import React from 'react';
export default class EditableCells extends React.Component {
state = {isEditMode: false, data: ""};
componentWillMount() {
this.setState({
isEditMode: this.props.isEditMode,
data: this.props.data
});
}
handleEditCell = (e) => {
this.setState({isEditMode: true});
};
handleKeyDown = (e) => {
switch (e.keyCode) {
case 13: //Enter
this.setState({isEditMode: false});
break;
}
};
handleChange = (e) => {
this.setState({data: e.target.value});
};
render() {
let {isEditMode, data} = this.state;
let cellHtml;
if (this.state.isEditMode) {
cellHtml = <input type="text" className="form-control" value={this.state.data} onKeyDown={this.handleKeyDown} onChange={this.handleChange} />
} else {
cellHtml = <div onClick={this.handleEditCell}>{this.state.data}</div>
}
return (
<td className="text-center">{cellHtml}</td>
);
}
}
Any help on how I would be able to do this would be very helpful, and, of course, much appreciated!
Thanks for your time
Upvotes: 1
Views: 3243
Reputation: 8376
React only pours data changes and, if necessary, re-renders the changed part of DOM from top to bottom. What you can do is give a child component access to parent component's state through props:
class ParentComponent extends Component {
handleWhatever(updated) {
this.setState({
whatever: updated
});
}
render() {
return (
<ChildComponent onEdit={::this.handleWhatever} />
);
}
}
and
class ChildComponent extends Component {
render() {
return (
<SomethingEditable onChange={this.props.onEdit} />
);
}
}
So, the idea is:
ParentComponent
, you define a method that, when called, changes this component's state, i.e. ParentComponent
's.ChildComponent
, you're having something that is capable of calling a function every time it is changed (the most common example: input
element and its onBlur
event).ChildComponent
, it calls the function that has been passed from ParentComponent
, which, in turn, provides data in bottom-to-top direction and delivers it to ParentComponent
.ParentComponent
, so setState
gets called then, which changes the state of ParentComponent
, and it reflects (or not) in re-render of the whole subtree (React takes care of picking only those components that really have to be re-rendered).This is a classic way to solve the problem of moving data in direction that is opposite to how React expects it to flow.
You can have as many nesting layers as you need, just don't forget to pass that function through all the layers.
Upvotes: 1
Reputation: 11339
Try to keep you application state at the highest most level in the component tree. The easy way out in this case is to let your Table component handle the data state, and also supply event handlers. Pass these down as props to the Row, and further down as needed. Proof of concept: https://jsfiddle.net/dannyjolie/4jfxeag8/1/
The general rule of thumb is to never use component state at all, and leverage all change in application change to the very top most component, but input fields is sometimes an exception from the rule as you may not want to modify the application state while typing.
The basics of it:
export default class Table extends React.Component {
constructor(props) {
super(props);
this.state.cells = ['Foo', 'Bar']; // Fetch something from your store, or better, pass the cells as props from parent component
this.handleSubmitEvent = this.handleSubmitEvent.bind(this);
this.handleChangeEvent = this.handleChangeEvent.bind(this);
}
handleSubmitEvent () {
// Up to date cells are stored in this.state
}
handleChangeEvent (value, cell) {
// All values are available in this.state, do with that as you wish :)
}
render () {
....
<Row handleSubmitEvent={this.handleSubmitEvent}
handleChangeEvent={this.handleChangeEvent}
data={this.state.cells} />
....
}
}
Upvotes: 4