user967710
user967710

Reputation: 2007

Reactjs add onChange to props.children dynamically

I'm using React v16 (latest) and I'm trying to create a general form component, that uses props.children.

export class MyForm extends React.Component {
  render() {
    return (
        <div>
            <div className="form">
                <h3>{this.props.formName}</h3>
                <div>
                  {React.Children.map(this.props.children, t => {return <span>{t}<br></br></span>;})}
                </div>
                <input type="button" value={this.props.formName} onClick={this.handleClick}/>
            </div>
        </div>
       )
    }
}

I want to create small form that just are able to create a meaningful json object and send it in POST

This is an example of such usage:

<MyForm>
   <input type="text" name="a1"></input>
   <input type="text" name="a2"></input>
</MyForm>

And I want to have many such small forms. Problem is I want each child (props.children) to have an onChange event -

onChange(event) // name is "a1" or "a2", like in the example aboce
{
 var obj = {};
 obj[name]=event.target.value;
 this.setState(obj);
}

-so that I don't need to manually add onChange for each such child -I guess another solution is to create a component, but I want the form to be flexible for each kind of sub-element (input text, text area, radio buttons,...) and I just want them all to have similar onChange that will set the name of the component and its value to the state...

I tried adding an onChange property in consturctor and in different hooks, but got:

cannot define property 'onChange', object is not extensible

So when are where (if at all) can I add an onChange dynamically to props.children

Upvotes: 1

Views: 3712

Answers (2)

Chase DeAnda
Chase DeAnda

Reputation: 16441

This is a great use case for a Higher Order Component. You can use a HOC to wrap and add the onChange prop to any component:

const WithOnChange = WrappedComponent => {
    return class extends Component {
        onChange = e => {
            const obj = {};
            obj[name]=e.target.value;
            this.setState(obj);
        }

        render() {
            return <WrappedComponent {...this.props} onChange={this.onChange} />
        }
    }
}

...

import Input from './Input';

class MyForm extends Component {
    render() {
        return (
            <form>
                ...
                <Input type="text" name="a1" />
                ...
            </form>
        )
    }
}

export default MyForm;

....

import WithOnChange from './WithOnChange';

const Input = (props) => (
    <input {...props} />
);

export default WithOnChange(Input);

EDIT:

Another option is to move your children map into a higher order component and then create a custom <Form /> component:

const Form = () => {
    return <form>{this.props.children}</form>
};

export default WithOnChange(Form);

const WithOnChange = WrappedComponent => {
    return class extends Component {
        onChange = e => {
            const obj = {};
            obj[name] = e.target.value;
            this.setState(obj);
        }

        render () {
            const children = React.Children.map(this.props.children, child => {
                return React.cloneElement(child, { onChange: this.onChange });
            });
            return <WrappedComponent {...this.props}>{children}</WrappedComponent>
        }
    }
}

Upvotes: 3

salman.zare
salman.zare

Reputation: 649

@user967710, can you please test the following code:

  constructor(props) {
    super(props);
    this.onChange = this.onChange.bind(this);
  }

  onChange(event) {
    debugger;
    var obj = {};
    obj.name = event.target.value;
    this.setState(obj);
  }
<MyForm formName="myForm">
  <input type="text" name="a1" onChange={this.onChange}></input>
  <input type="text" name="a2" onChange={this.onChange}></input>
</MyForm>

Upvotes: 0

Related Questions