0

In a React app I have a couple of working components like this one:

<div className="form-group col-md-6">
  <label className='inp-lbl'>{utl.nameWord()}</label>
  <div className='inp-name'>
    <input
      type="text"
      ref="name"
      defaultValue={this.name}
      onChange={e => {
        this.setState((state) => {
          return {name: e.target.value};
        });
      }}
    />
  </div>
</div>

Instead of repeating similar code for each or them, I want to extract a generic component that I will be able to reuse; but it does not work as I expect. Here is the new component I made:

class InputField extends React.Component {
    constructor(props) {
      super(props);
      this.state = {
        labelStr: props.lbStr,
        nameStr: props.nmStr,
        defltVal: props.dfVl,
        onChgFnc: props.onChFn
      };
      // this.onChgFnc = props.onChFn
    }

    render() {
      return (
        <React.Fragment>
          <div className="form-group col-md-6">
            <label className='inp-lbl'>{this.state.labelStr}</label>
            <div className='inp-name'>
              <input
                type="text"
                ref={this.state.nameStr}
                defaultValue={this.state.defltVal}
                onChange={this.state.onChgFnc}
              />
            </div>
          </div>
        </React.Fragment>
      )
    }
}

And this is how I call the new component:

<InputField lbStr={utl.nameWord()} nmStr='name' 
    dfVl={this.name}
    onChFn={handleChange} />

The function handleChange is defined as:

function handleChange(event) {
    this.setState((state) => {
      return {name: event.target.value};
    })
}

Though I thought this should work, it does not. So it would be great if somebody could spot the mistake I am making and let me know.

Michel
  • 10,303
  • 17
  • 82
  • 179
  • Can you give any more detail about what the actual error is? – SuperJumbo Dec 29 '22 at 04:17
  • It is an **anti-pattern** to store `props` in `state`. Read this article for why it's the case: https://reactpatterns.js.org/docs/props-in-initial-state-is-an-anti-pattern/ – EternalObserver Dec 29 '22 at 04:26
  • Does this answer your question? [React component initialize state from props](https://stackoverflow.com/questions/40063468/react-component-initialize-state-from-props) – EternalObserver Dec 29 '22 at 04:37

3 Answers3

1

I don’t understand why you need to assign the onChangehandler from props to state object in Child Component. You can directly bind the change handler from props’ change callback function.

 <input type="text"
     ref={this.state.nameStr}
     defaultValue={this.state.defltVal}
     onChange={this.props.onChFn} />
1

You could be suffering from an incorrectly bound this inside your handleChange function. Perhaps try just closing over the setState call instead of referencing it via this. For example

function handleChange(event) {
  setState({ name: event.target.value })
}

(I've also changed the particular usage of the state-setter to accept a value as you weren't using the previous state value)

I would also go one further and change to a functional component and don't store your props in state as this will disconnected the component from the flow of prop values if/when they change.

function InputField({ name, label, onChange, defaultValue }){
  return (
    <div className="form-group col-md-6">
      <label className='inp-lbl'>{label}</label>
      <div className='inp-name'>
        <input
          type="text"
          ref={name}
          defaultValue={defaultValue}
          onChange={onChange}
        />
      </div>
    </div>
  )
}
SuperJumbo
  • 519
  • 3
  • 13
0

Storing props in state is an anti-pattern in React as it often leads to problem of multiple sources of truth(same info) in a component. A better way to write your component would be to initialize the value of field from props and derive rest of the values directly from props.

You can directly bind the change handler from props’ change callback function.

class InputField extends React.Component {
    constructor(props) {
      super(props);
      this.state = {
        nameStr: props.nmStr,
      };
    }

    render() {
      return (
        <React.Fragment>
          <div className="form-group col-md-6">
            <label className='inp-lbl'>{props.lbStr}</label>
            <div className='inp-name'>
              <input
                type="text"
                value={this.state.nameStr}
                defaultValue={this.props.defltVal}
                onChange={this.props.onChgFnc}
              />
            </div>
          </div>
        </React.Fragment>
      )
    }
}
EternalObserver
  • 517
  • 7
  • 19