7

So im trying to make react work with ES6 syntax. In ES5 I had setInitialState without a constructor which worked with the function binding syntax. I have a list of prices which is arbitrary and I want the state to change when the input element is changed. But the right price has to be changed.

I'm not even quite sure this is the right way to do it. Can someone please tell me the most recent way this should be done?

Here is my code:

import React, {Component} from 'react'
import 'bootstrap/dist/css/bootstrap.css';

export default class PriceTable extends Component {
  constructor(props, context) {
    super(props, context);

    this.state = {
      pid: this.props.pid || "12345",
      name: this.props.name || "name",
      prices: this.props.prices || [
        {count: 1, desc: 'one', price: 8.25},
        {count: 6, desc: 'six', price: 7.60},
        {count: 12, desc: 'twelve', price: 6.953}
      ]
    };

    this.setPid = this.setPid.bind(this);
    this.setName = this.setName.bind(this);
    this.setCount = this.setCount.bind(this, i);
    this.setDesc = this.setDesc.bind(this, i);
    this.setPrice = this.setPrice.bind(this, i);
    this.logDebug = this.logDebug.bind(this);
  }

  setPid(e) {
    this.setState({pid: e.target.value})
  }

  setName(e) {
    this.setState({name: e.target.value})
  }

  setCount(i, e) {
    var newPrices = this.state.prices
    newPrices[i].count = e.target.value
    this.setState({prices: newPrices})
  }

  setDesc(i, e) {
    var newPrices = this.state.prices
    newPrices[i].sec = e.target.value
    this.setState({prices: newPrices})
  }

  setPrice(i, e) {
    var newPrices = this.state.prices
    newPrices[i].price = e.target.value
    this.setState({prices: newPrices})
  }

  _renderPriceRow(price, i) {
    return (
      <tr key={i}>
        <td >
          <input type="text" className="form-control" defaultValue={price.count} onChange={this.setCount(this, i).bind(this, i)}/>
        </td>
        <td >
          <input type="text" className="form-control" defaultValue={price.desc} onChange={this.setDesc(this, i).bind(this, i)}/>
        </td>
        <td >
          <input type="text" className="form-control" defaultValue={price.price} onChange={this.setPrice(this, i).bind(this, i)}/>
        </td>
      </tr>
    );
  }

  render() {
    return (
      <div className="row">
       ...
      </div>
    );
  }
}

And this is the error...

PriceTable.jsx:21 Uncaught ReferenceError: i is not defined
    at new PriceTable (PriceTable.jsx:21)

2 Answers 2

14

You are binding the functions incorrectly

In your constructor you need not specify the argument, you only need to bind it like this.setDesc = this.setDesc.bind(this);

Also In your onChange, when you want to pass paramters to the function you specify it with bind and not pass as arguments and bind them again .

onChange={this.setDesc.bind(this, i)}

You entire code will look like

import React, {Component} from 'react'
import 'bootstrap/dist/css/bootstrap.css';

export default class PriceTable extends Component {
  constructor(props, context) {
    super(props, context);

    this.state = {
      pid: this.props.pid || "12345",
      name: this.props.name || "name",
      prices: this.props.prices || [
        {count: 1, desc: 'one', price: 8.25},
        {count: 6, desc: 'six', price: 7.60},
        {count: 12, desc: 'twelve', price: 6.953}
      ]
    };

    this.setPid = this.setPid.bind(this);
    this.setName = this.setName.bind(this);
    this.setCount = this.setCount.bind(this);
    this.setDesc = this.setDesc.bind(this);
    this.setPrice = this.setPrice.bind(this);
    this.logDebug = this.logDebug.bind(this);
    this._renderPriceRow = this._renderPriceRow.bind(this);
  }

  setPid(e) {
    this.setState({pid: e.target.value})
  }

  setName(e) {
    this.setState({name: e.target.value})
  }

  setCount(i, e) {
    var newPrices = this.state.prices
    newPrices[i].count = e.target.value
    this.setState({prices: newPrices})
  }

  setDesc(i, e) {
    var newPrices = this.state.prices
    newPrices[i].sec = e.target.value
    this.setState({prices: newPrices})
  }

  setPrice(i, e) {
    var newPrices = this.state.prices
    newPrices[i].price = e.target.value
    this.setState({prices: newPrices})
  }

  _renderPriceRow(price, i) {
    return (
      <tr key={i}>
        <td >
          <input type="text" className="form-control" defaultValue={price.count} onChange={this.setCount.bind(this, i)}/>
        </td>
        <td >
          <input type="text" className="form-control" defaultValue={price.desc} onChange={this.setDesc.bind(this, i)}/>
        </td>
        <td >
          <input type="text" className="form-control" defaultValue={price.price} onChange={this.setPrice.bind(this, i)}/>
        </td>
      </tr>
    );
  }

  render() {
    return (
      <div className="row">
       ...
      </div>
    );
  }
}

You could also make use of Arrow functions to pass parameters like

onChange={(e) => this.setDesc(e,  i)}
Sign up to request clarification or add additional context in comments.

4 Comments

This has actually the same effect as of not binding the function at all. I get the following error: Uncaught TypeError: Cannot read property 'setCount' of undefined at _renderPriceRow
Thats because you haven't bind the renderPriceRow function. Check the update
Can you please briefly explain why I have to bind that function too? Is it because the function is out of scope of the actual class? This seems like a strange amount of boilerplate code here. Why is it not bound like in ES5 before?
You need to bind that function too because it uses this as a reference to call the setCount function and where this in the function refers to the context of the function itself if you don't bind it. much like binding other functions which use setState
2

Use the onChange method like this:

onChange={this.setCount.bind(this, i)}
onChange={this.setDesc.bind(this, i)}
onChange={this.setPrice.bind(this, i)}

Remove these lines from constructor:

this.setCount = this.setCount.bind(this, i);
this.setDesc = this.setDesc.bind(this, i);
this.setPrice = this.setPrice.bind(this, i);

We can bind the function in constructor, where no extra parameter is required, like the these functions in your case:

this.setPid = this.setPid.bind(this);
this.setName = this.setName.bind(this);
this.logDebug = this.logDebug.bind(this);

But if in any function you want to pass any extra parameter, then you need to use either arrow function or bind that function by using .bind(this, parameter).

Note:

Another thing that you need to change here is, the way you are updating the state values, You should never mutate state variable directly. When you assign an array to any variable, that variable will have the reference of that array, means whatever changes you will do to that variable will affect the original array.

So you need to create a copy of that variable and then do the changes on that and use setState to update the values.

As per DOC:

Never mutate this.state directly, as calling setState() afterwards may replace the mutation you made. Treat this.state as if it were immutable.

4 Comments

Ok I will do the copy stuff. Actually the lines you suggest to remove were my naive approch to fix the previous error: Uncaught TypeError: Cannot read property 'setCount' of undefined at _renderPriceRow
no that not the way, whatever i suggested will work properly, i am sure about that, will provide a working code :)
becuas eyou didn't binded _renderPriceRow function.
write this line in the constructor: this._renderPriceRow = this._renderPriceRow.bind(this);

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.