2

I am a professional web developer teaching myself react. I created this table as part of a larger form. animated gif showing me clicking buttons to add and remove rows 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>
        );
    }
}
0

1 Answer 1

3

In your ProductsList's render(), change the array map to something like:

{this.props.products.map((product, index) => <Product key={product.timestamp} data={product} index={index} products={this}/>)}

Then in your Product's change the updateField() to:

updateField(...props){
    this.props.products.updateProduct(this.props.index, ...props)
}

And finally, change ProductsList's updateProduct() to:

updateProduct(index, field, newValue) {
    const products = this.props.products.map((product, productIndex)) => {
        if (index === productIndex) {
            return {
                ...product,
                [field]: newValue
            };
        }
        return product;
    })
    this.props.onChange(products);
}

Also, there's a slight typo in Product render. The FormControl's onClick should read onClick={e => this.updateField('species', e.target.value)}.

Sign up to request clarification or add additional context in comments.

2 Comments

cool trick using ... to populate the values on a new object, but you don't do anything with the new object. I am missing the step where you replace the product on the products array without editing props. are there any shortcuts other than copying the whole products array and then splicing in our new object?
that wasn't as bad as I was envisioning it for that last bit, thanks!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.