简体   繁体   中英

How to deal with dynamically created controlled inputs in React?

So I've been trying to create multiple dynamically created controlled inputs. But if I add an input pressing the add ingredient button the newly created input doesn't retain focus when I add text to it.
Weird thing(or clue) is the first input does retain focus after adding a new one, just the added ones don't work properly.

Made my question in pseudo-code format for clarity:

if(Not react way of creating multiple dynamically created controlled inputs) {  
 question = How to create multiple dynamically created controlled inputs in React way?  
} else {  
 question = How to make the dynamically created input retain focus?  
}

the code is(working jsfiddle snippet) :

 class Modal extends React.Component { constructor(props){ super(props) this.state = { ingredients: [{ value: 'aap'}] } } addIngredient(e) { const old = this.state.ingredients const newState = [...old, {value: ''}] this.setState( {ingredients: newState} ) } handleInput(e, i) { console.log(e.target.value) var newState = this.state.ingredients newState[i].value = e.target.value /*var promise = new Promise( (resolve, reject) => { return this.setState({ ingredients: newState}) }) promise.then( () => {return e.target.focus()} ) */ this.setState( { ingredients: newState } ) e.target.focus() } render() { const inputs = this.state.ingredients return ( <div className="modal"> <div className="modal-box"> <form> <h1>Create your recipe</h1> <div> <label>Recipe name:</label> <input type="text" /> </div> <div> <label>Ingredients:</label> {inputs.map( (input, i) => ( <input value={input.value} onChange={(e) => this.handleInput(e, i)} key={`${i}-${i * Math.random()}`} /> ))} <button id="ingredient-button" type="button" onClick={(e) => this.addIngredient(e)}><span>+</span>add ingredient</button> </div> </form> </div> </div> ); } } ReactDOM.render(<Modal/>, document.getElementById('root')) 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script> <div id='root'></div> 

Since your key is generated with random numbers, each time the DOM re-renders it'll not recognize it as the same input, thus loses focus. I've changed the key to the below to make it work.

key={`ingredients-${i}`}

 class Modal extends React.Component { constructor(props){ super(props) this.state = { ingredients: [{ value: 'aap'}] } } addIngredient(e) { const old = this.state.ingredients const newState = [...old, {value: ''}] this.setState( {ingredients: newState} ) } handleInput(e, i) { var newState = this.state.ingredients newState[i].value = e.target.value /*var promise = new Promise( (resolve, reject) => { return this.setState({ ingredients: newState}) }) promise.then( () => {return e.target.focus()} ) */ this.setState( { ingredients: newState } ) } render() { const inputs = this.state.ingredients return ( <div className="modal"> <div className="modal-box"> <form> <h1>Create your recipe</h1> <div> <label>Recipe name:</label> <input type="text" /> </div> <div> <label>Ingredients:</label> {inputs.map( (input, i) => ( <input value={input.value} onChange={(e) => this.handleInput(e, i)} key={`ingredients-${i}`} /> ))} <button id="ingredient-button" type="button" onClick={(e) => this.addIngredient(e)}><span>+</span>add ingredient</button> </div> </form> </div> </div> ); } } ReactDOM.render(<Modal/>, document.getElementById('root')) 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script> <div id='root'></div> 

Credit to Nelson Yeung for finding the bug with the keys. But to also get focus on each created input it helps to make them their own class components. That way you get access to the life-cycle methods. They may still be controlled, but they will get their onChange callback passed down to them as props.

I use ref together with componentDidMount() to focus the new component exactly once (just the first time it's mounted).

class Input extends React.Component {
    constructor(props){
    super(props);
    this.ref = null;
  }
  componentDidMount(){
    this.ref.focus();
  }
  render(){
    const {value, handleOnChange} = this.props
    return <input value={ value } onChange={ handleOnChange } ref={ el => this.ref = el} />
  }
}

class Modal extends React.Component {
  constructor(props){
    super(props)
    this.state = {
      ingredients: [{ value: 'aap'}]
    }
  }
  addIngredient(e) {
    const old = this.state.ingredients
    const newState = [...old, {value: ''}];

    this.setState(
      {ingredients: newState}
    )
  }
  handleInput(i) {
  // return callback with the right index
    return (e) => {
      var newState = this.state.ingredients
      newState[i].value = e.target.value
      this.setState( { ingredients: newState } )
    }
  }

  render() {
    const inputs = this.state.ingredients
    return (
      <div className="modal">
        <div className="modal-box">
          <form>
            <h1>Create your recipe</h1>
            <div>
              <label>Recipe name:</label>
              <input type="text" />
            </div>
            <div>
            <label>Ingredients:</label>
            {inputs.map( (input, i) => (
              <Input 
                value={input.value} 
                handleOnChange={this.handleInput(i)} 
                key={i}
              />
            ))}
              <button id="ingredient-button" type="button" onClick={(e) => this.addIngredient(e)}><span>+</span>add ingredient</button>
            </div>
          </form>

        </div>
      </div>
    );
  }
}

ReactDOM.render(<Modal/>, document.getElementById('root'))

Working code example here: https://jsfiddle.net/jonahe/2ga02g6h/

Edit : I now realize I may have read your question too quickly. Maybe you didn't need/want/ask for focus on the newly created input fields.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM