2

I've been using React for a few months now, and one of the things I'm finding most difficult is how to properly bind functions that take arguments.

Currently, I have three inputs that share a single update function, but require a different first argument to be passed. Here is my component:

class MyComponent extends Component {
  render() {
    const { onChange } = this.props;
    return(
      <div className='my_component'>
        <Row>
          <Column>
            <Input 
              value={item1} 
              onChange={ (newValue) => onChange('item1', newValue) } />
          </Column>
        </Row>
        <Row>
          <Column>
            <Input 
              value={item2} 
              onChange={ (newValue) => onChange('item2', newValue) } />
          </Column>
        </Row>
        <Row>
          <Column>
            <Input 
              value={item3} 
              onChange={ (newValue) => onChange('item3', newValue) } />
          </Column>
        </Row>
      </div>
    );
  }
}

So, currently, I'm using arrow functions in my render function of the component. But through research, I've found that obviously has performance issue in terms of re-rendering.

The solution offered is to bind in the constructor using

constructor() {
  super();
  this.handleChange = this.handleChange.bind(this)
}

handleChange(event) {
  this.props.onChange('ARGUMENT REQUIRED!', event.target.value);
}

The problem is, that I cannot get that first argument to work... Am I supposed to create a function for each and bind one for each in the constructor, like so:

constructor() {
  super();
  this.handleItem1Change= this.handleItem1Change.bind(this)
  this.handleItem2Change= this.handleItem2Change.bind(this)
  this.handleItem3Change= this.handleItem3Change.bind(this)
}

handleItem1Change(newValue) {
    this.props.onChange('item1', newValue);
}
handleItem2Change(event) {
    this.props.onChange('item2', newValue);
}
handleItem3Change(event) {
    this.props.onChange('item3', newValue);
}

That seems repetitive...is there a more streamlined way to do this?

3
  • Have you tried: onChange={ (newValue) => onChange.bind(this, 'item1') } /> Commented Mar 22, 2017 at 19:10
  • Yes and store this 'item1' in some kind of refs for the input element. So when u call: onChange={ (newValue) => onChange(this, this.refs) }... just check once for using the strings in ref. Commented Mar 22, 2017 at 19:13
  • @bergi The newValue is passed from the input callback, and I messed up the event.target.value that is was is passed up, so it is just the new value. That is relatively irrelevant to the problem at hand, though. Commented Mar 22, 2017 at 19:21

4 Answers 4

2

If you have control of the Input component, why not have a prop such as name and then in the Input component pass in the onChange function as a prop.

In the Input component whereever you are handling the change you could just do.

<Input 
 value={item3} 
 onChange={ onChange } 
 name='item3'

/>

// in the Input component
handleChange = (value) => {
  this.props.onChange(value, this.props.name)
}

and then you would just need to update your onChange to put the value first and the name second. Reason for doing it that way is to ensure this doesn't break your Input component in the other places that it is used since value will still be the first argument, and name is a secondary optional argument.

if you are passing the event back in the onChange instead of value you can still use the event and just do e.target.name as long as you are applying the name prop to the input thats rendered in Input and would look like:

handleChange(event) {
  this.props.onChange(event.target.name, event.target.value);
}
Sign up to request clarification or add additional context in comments.

2 Comments

I like this a lot. This definitely works. I originally didn't have a name prop, but I probably should have anyway!
What I have found is that anytime you are needing to reuse a function and don't want to bind the arguments, then you just need to have a component that can be delegated as the provider of said argument based on its props.
1

You can pass arguments that you want to partially apply right into bind:

constructor() {
  super();
  this.handleItem1Change = this.props.onChange.bind(this, 'item1');
  this.handleItem2Change = this.props.onChange.bind(this, 'item2');
  this.handleItem3Change = this.props.onChange.bind(this, 'item3');
}

Alternatively, you can still use arrow functions there:

constructor() {
  super();
  this.handleItem1Change = newValue => this.props.onChange('item1', newValue);
  this.handleItem2Change = newValue => this.props.onChange('item2', newValue);
  this.handleItem3Change = newValue => this.props.onChange('item3', newValue);
}

3 Comments

Right, but that's essentially the same as my second solution. It definitely works, but is relatively repetitive. Was hoping there might be a way to do it with a single bound function instead of declaring a new binding for each.
@TomNolan Of course you can do that in a loop or whatever, but for only three items that might not save a lot
Btw, I guess the most idiomatic React way would be to let the Row (or Column or Input) component handle all this, so that you don't have to repeat it in MyComponent
0

Just add a new layer of abstraction, something like this::

form.js //use the component RowInput
<RowInput name={'item1'} value={'item'} onChange={onChange} />

RowInput.js // stupid/dumb component
export const RowInput = (name, item, onChange)=>
          <Column>
            <Input 
              value={item} 
              onChange={ (val) => onChange(name) } />
          </Column>

Comments

-1

you could try this to bind the on change callback

<Input value={item1} onChange={onChange.bind(null,'item1') } />

the callback will look like this

onChange(item, event) 

1 Comment

This will cause the same issue that the OP is trying to solve which is not binding the function in the render method which will cause unnecessary rerenders on the component

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.