yihangho
yihangho

Reputation: 2215

React custom controlled form component events

When we use the builtin HTML input components, the onChange listener is given an instance of SyntheticEvent, which allows us to do things like

onChange = (event) => {
  this.setState({
    [event.target.name]: event.target.value
  })
};

render() {
  return (
    <div>
      <input 
        type="text" 
        name="username" 
        value={ this.state.username }
        onChange={ this.onChange } />

      <input 
        type="password" 
        name="password" 
        value={ this.state.password }
        onChange={ this.onChange } />
    </div>
  );
}

Notice that I can use the same listener for multiple form controls. However, if we use a custom form control, say, a DatePicker or something more domain-specific like UserSelector, these components usually have their own API to handle changes (e.g., onChange(newValue) or onChange(oldValue, newValue)). This forces me to write one listener per field.

Is there any good way to deal with this situation?

Upvotes: 1

Views: 427

Answers (1)

user7572807
user7572807

Reputation:

One approach would be to construct your own data structure to emit that is similar to an event.

import React, { PureComponent } from 'react';

const onChange = component => event => {
  component.props.onChange({
    target: {
      value: Object.assign({}, component.props.value, {
        [event.target.name]: event.target.value,
      })
    }
  })
};

export default class UserControl extends PureComponent {
  render () {
    return (
      <div>
        <input name="name" value={this.props.value.name} onChange={onChange(this)} />
        <input name="age" value={this.props.value.age} onChange={onChange(this)} />
      </div>
    );
  }
};

Your custom event object can pluck other keys of React's synthetic event as required.

Note that the onChange handler doesn't have to be tied to a class. Since it's generic, it can exist as a separate bit of code that accepts a component first.

EDIT: One thing to consider is to also pass along the stopPropagation method in case it's needed. I haven't had to do this myself yet, but this seems to work. Note the event.persist(). This ensures the SyntheticEvent is not reused by React.

const onChange = component => event => {
  event.persist();
  component.props.onChange({
    stopPropagation: event.stopPropagation.bind(event),
    target: {
      value: Object.assign({}, component.props.value, {
        [event.target.name]: event.target.value
      })
    }
  });
};  

Upvotes: 2

Related Questions