title: React: Proper way to update an array of objects in state
body: <p>I am a professional web developer teaching myself react. I created this table as part of a larger form.
  <a href="https://i.stack.imgur.com/StYcN.gif" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/StYcN.gif" alt="animated gif showing me clicking buttons to add and remove rows"></a>
  The table is invoked inside the form component</p>
  
  <pre><code>&lt;ProductList
      products={this.state.products}
      onChange={products =&gt; this.sendUpdate('products', products)}
  /&gt;
  </code></pre>
  
  <p>this.sendUpdate:</p>
  
  <pre><code>sendUpdate(field, value) {
      this.setState({[field]: value});
      socket.emit('updateItem', this.state.id, {[field]: value});
  }
  </code></pre>
  
  <p>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:</p>
  
  <pre><code>&lt;tbody&gt;
      {this.props.products.map((product, i) =&gt;
          &lt;Product key={i} data={product} products={this}/&gt;
      )}
  &lt;/tbody&gt;
  </code></pre>
  
  <p>What is the proper way to update the state when I type in one of the inputs?</p>
  
  <pre><code>&lt;FormControl
      value={this.props.data.species}
      onClick={e =&gt; this.updateProduct('species', e.target.value)}
  /&gt;
  </code></pre>
  
  <p>full code for ProductList</p>
  
  <pre><code>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 (
              &lt;tr&gt;
                  &lt;td&gt;
                      &lt;FormControl
                          value={this.props.data.species}
                          onClick={e =&gt; this.updateProduct('species', e.target.value)}
                      /&gt;
                  &lt;/td&gt;
                  &lt;td&gt;&lt;FormControl/&gt;&lt;/td&gt;
                  &lt;td&gt;&lt;FormControl/&gt;&lt;/td&gt;
                  &lt;td&gt;&lt;FormControl/&gt;&lt;/td&gt;
                  &lt;td&gt;&lt;FormControl/&gt;&lt;/td&gt;
                  &lt;td&gt;&lt;FormControl/&gt;&lt;/td&gt;
                  &lt;td&gt;&lt;FormControl type="number"/&gt;&lt;/td&gt;
                  &lt;td&gt;&lt;Button bsStyle="danger" onClick={() =&gt; this.props.products.deleteProduct(this.props.data)}&gt;X&lt;/Button&gt;&lt;/td&gt;
              &lt;/tr&gt;
          );
      }
  }
  
  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 =&gt; {
              return p !== product
          });
          this.props.onChange(products);
      }
  
      render() {
          return (
              &lt;Table responsive&gt;
                  &lt;thead&gt;
                  &lt;tr&gt;
                      &lt;th&gt;Species&lt;/th&gt;
                      &lt;th&gt;Dried&lt;/th&gt;
                      &lt;th&gt;Cut&lt;/th&gt;
                      &lt;th&gt;Dimensions Green&lt;/th&gt;
                      &lt;th&gt;Dimensions Dry&lt;/th&gt;
                      &lt;th&gt;Color&lt;/th&gt;
                      &lt;th&gt;Quantity&lt;/th&gt;
                      &lt;th className="text-right"&gt;
                          &lt;Button bsStyle="success" bsSize="xsmall" onClick={() =&gt; this.addProduct()}&gt;Add&lt;/Button&gt;
                      &lt;/th&gt;
                  &lt;/tr&gt;
                  &lt;/thead&gt;
                  &lt;tbody&gt;
                  {this.props.products.map(product =&gt; &lt;Product key={product.timestamp} data={product} products={this}/&gt;)}
                  &lt;/tbody&gt;
              &lt;/Table&gt;
          );
      }
  }
  </code></pre>
  
  <p>This is what I ended up with based on the accepted answer:</p>
  
  <pre><code>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 =&gt; {
              return p === product ? {...p, [field]: newValue} : p;
          });
          this.props.onChange(products);
      }
  
      deleteProduct(product) {
          let products = this.props.products.filter(p =&gt; {
              return p !== product
          });
          this.props.onChange(products);
      }
  
      render() {
          return (
              &lt;Table responsive striped&gt;
                  &lt;thead&gt;
                  &lt;tr&gt;
                      &lt;th&gt;Species&lt;/th&gt;
                      &lt;th&gt;Dried&lt;/th&gt;
                      &lt;th&gt;Cut&lt;/th&gt;
                      &lt;th&gt;Dimensions Green&lt;/th&gt;
                      &lt;th&gt;Dimensions Dry&lt;/th&gt;
                      &lt;th&gt;Color&lt;/th&gt;
                      &lt;th&gt;Quantity&lt;/th&gt;
                      &lt;th className="text-right"&gt;
                          &lt;Button bsStyle="success" bsSize="xsmall" onClick={() =&gt; this.addProduct()}&gt;Add&lt;/Button&gt;
                      &lt;/th&gt;
                  &lt;/tr&gt;
                  &lt;/thead&gt;
                  &lt;tbody&gt;
                  {this.props.products.map((product, i) =&gt; this.renderRow(i, product, this))}
                  &lt;/tbody&gt;
              &lt;/Table&gt;
          );
      }
  
      renderRow(i, product) {
          return (
              &lt;tr key={i}&gt;
                  &lt;td&gt;
                      &lt;FormControl
                          value={product.species || ''}
                          onChange={e =&gt; this.updateProduct(product, 'species', e.target.value)}
                      /&gt;
                  &lt;/td&gt;
                  &lt;td&gt;
                      &lt;FormControl
                          value={product.dried || ''}
                          onChange={e =&gt; this.updateProduct(product, 'dried', e.target.value)}
                      /&gt;
                  &lt;/td&gt;
                  &lt;td&gt;
                      &lt;FormControl
                          value={product.cut || ''}
                          onChange={e =&gt; this.updateProduct(product, 'cut', e.target.value)}
                      /&gt;
                  &lt;/td&gt;
                  &lt;td&gt;
                      &lt;FormControl
                          value={product.dimensionsGreen || ''}
                          onChange={e =&gt; this.updateProduct(product, 'dimensionsGreen', e.target.value)}
                      /&gt;
                  &lt;/td&gt;
                  &lt;td&gt;
                      &lt;FormControl
                          value={product.dimensionsDry || ''}
                          onChange={e =&gt; this.updateProduct(product, 'dimensionsDry', e.target.value)}
                      /&gt;
                  &lt;/td&gt;
                  &lt;td&gt;
                      &lt;FormControl
                          value={product.color || ''}
                          onChange={e =&gt; this.updateProduct(product, 'color', e.target.value)}
                      /&gt;
                  &lt;/td&gt;
                  &lt;td&gt;
                      &lt;FormControl
                          type="number"
                          value={product.quantity || 0}
                          onChange={e =&gt; this.updateProduct(product, 'quantity', e.target.value)}
                      /&gt;
                  &lt;/td&gt;
                  &lt;td&gt;&lt;Button bsStyle="danger" onClick={() =&gt; this.deleteProduct(product)}&gt;X&lt;/Button&gt;&lt;/td&gt;
              &lt;/tr&gt;
          );
      }
  }
  </code></pre>