I am a professional web developer teaching myself react. I created this table as part of a larger form.
The table is invoked inside the form component
<ProductList
products={this.state.products}
onChange={products => this.sendUpdate('products', products)}
/>
this.sendUpdate:
sendUpdate(field, value) {
this.setState({[field]: value});
socket.emit('updateItem', this.state.id, {[field]: value});
}
That part is all working great with all my form updates. but now I am trying to figure out how to process the updates inside the table. Each product is a row of the table invoked like this:
<tbody>
{this.props.products.map((product, i) =>
<Product key={i} data={product} products={this}/>
)}
</tbody>
What is the proper way to update the state when I type in one of the inputs?
<FormControl
value={this.props.data.species}
onClick={e => this.updateProduct('species', e.target.value)}
/>
full code for ProductList
import React from "react";
import {Button, Table, FormControl} from "react-bootstrap";
class Product extends React.Component {
updateField(...props){
this.props.products.updateProduct(this.data, ...props)
}
render() {
return (
<tr>
<td>
<FormControl
value={this.props.data.species}
onClick={e => this.updateProduct('species', e.target.value)}
/>
</td>
<td><FormControl/></td>
<td><FormControl/></td>
<td><FormControl/></td>
<td><FormControl/></td>
<td><FormControl/></td>
<td><FormControl type="number"/></td>
<td><Button bsStyle="danger" onClick={() => this.props.products.deleteProduct(this.props.data)}>X</Button></td>
</tr>
);
}
}
export default class ProductList extends React.Component {
constructor(...props) {
super(...props);
}
addProduct() {
let products = this.props.products.concat([{timestamp: Date.now()}]);
this.props.onChange(products);
}
updateProduct(product, field, newValue) {
this.props.products;
// ???
}
deleteProduct(product) {
let products = this.props.products.filter(p => {
return p !== product
});
this.props.onChange(products);
}
render() {
return (
<Table responsive>
<thead>
<tr>
<th>Species</th>
<th>Dried</th>
<th>Cut</th>
<th>Dimensions Green</th>
<th>Dimensions Dry</th>
<th>Color</th>
<th>Quantity</th>
<th className="text-right">
<Button bsStyle="success" bsSize="xsmall" onClick={() => this.addProduct()}>Add</Button>
</th>
</tr>
</thead>
<tbody>
{this.props.products.map(product => <Product key={product.timestamp} data={product} products={this}/>)}
</tbody>
</Table>
);
}
}
This is what I ended up with based on the accepted answer:
import React from "react";
import {Button, Table, FormControl} from "react-bootstrap";
export default class ProductList extends React.Component {
constructor(...props) {
super(...props);
}
addProduct() {
let products = this.props.products.concat([{}]);
this.props.onChange(products);
}
updateProduct(product, field, newValue) {
const products = this.props.products.map(p => {
return p === product ? {...p, [field]: newValue} : p;
});
this.props.onChange(products);
}
deleteProduct(product) {
let products = this.props.products.filter(p => {
return p !== product
});
this.props.onChange(products);
}
render() {
return (
<Table responsive striped>
<thead>
<tr>
<th>Species</th>
<th>Dried</th>
<th>Cut</th>
<th>Dimensions Green</th>
<th>Dimensions Dry</th>
<th>Color</th>
<th>Quantity</th>
<th className="text-right">
<Button bsStyle="success" bsSize="xsmall" onClick={() => this.addProduct()}>Add</Button>
</th>
</tr>
</thead>
<tbody>
{this.props.products.map((product, i) => this.renderRow(i, product, this))}
</tbody>
</Table>
);
}
renderRow(i, product) {
return (
<tr key={i}>
<td>
<FormControl
value={product.species || ''}
onChange={e => this.updateProduct(product, 'species', e.target.value)}
/>
</td>
<td>
<FormControl
value={product.dried || ''}
onChange={e => this.updateProduct(product, 'dried', e.target.value)}
/>
</td>
<td>
<FormControl
value={product.cut || ''}
onChange={e => this.updateProduct(product, 'cut', e.target.value)}
/>
</td>
<td>
<FormControl
value={product.dimensionsGreen || ''}
onChange={e => this.updateProduct(product, 'dimensionsGreen', e.target.value)}
/>
</td>
<td>
<FormControl
value={product.dimensionsDry || ''}
onChange={e => this.updateProduct(product, 'dimensionsDry', e.target.value)}
/>
</td>
<td>
<FormControl
value={product.color || ''}
onChange={e => this.updateProduct(product, 'color', e.target.value)}
/>
</td>
<td>
<FormControl
type="number"
value={product.quantity || 0}
onChange={e => this.updateProduct(product, 'quantity', e.target.value)}
/>
</td>
<td><Button bsStyle="danger" onClick={() => this.deleteProduct(product)}>X</Button></td>
</tr>
);
}
}