Mossmyr
Mossmyr

Reputation: 979

React: How can I control a list of <input> with state?

Here's a minimal working example of a component with a form of inputs created from a list, and one created from a single value:

import React from 'react';

export default class App extends React.Component {
  constructor(props) {
    super(props);
  }

  state = {
    value: 'single value',
    values: ['one', 'two'],
  }

  render() {
    let test = this.state.values.map( (v, i) => {
      return <input
        key={ v }
        value={ v }
        onChange={ (e) => {
          let values = this.state.values;
          values[i] = e.target.value;
          this.setState({ values });
        } }
      />;
    } )

    return <div>
      <form>
        { test }
      </form>
      <form>
        <input
          value={ this.state.value }
          onChange={ (e) => this.setState({ value: e.target.value }) }
        />
      </form>
    </div>;
  }
}

Which produces this:

enter image description here

When the user edits the third input field ('single value'), everything is fine. The user types something, the state updates, everyone is happy.

When the user edits either of the first two input fields however, the cursor loses focus immediately after the state is updated. To edit this field, the user must click on the field after any letter has been put in.

How do I solve this? Is there a way to prevent the component from blurring the <input>, or is there a better way to solve this?

I don't know how to record my screen and make a GIF from it. If you did not understand what I mean I suggest you create a React app with npx create-react-app my-app and edit my-app/src/App.js to my code example.

Upvotes: 4

Views: 12949

Answers (2)

Nadhu
Nadhu

Reputation: 1

In render(), you are mapping the state 'values' to the <input>. React identifies the component by its key.

1. Initially you have ['one', 'two'] in 'values' state. And its corresponding keys will be ['one', 'two'].

2. If suppose you change the value of one of the input element as "one1". Then you will update the states and while mapping you will have a new key as "one1" which is different from previous key "one", so React considers it as a new element and loses the focus.

You try with this code

let test = this.state.values.map( (v, i) => {
      return <input
        key={ `key${i}` }
        value={ v }
        onChange={ (e) => {
          let values = this.state.values;
          values[i] = e.target.value;
          this.setState({ values });
        } }
      />;
    } )

You can check the code here. https://stackblitz.com/edit/react-9lstyj

Upvotes: 0

Sagiv b.g
Sagiv b.g

Reputation: 31014

The problem is that you are passing the value as a key, for each new value you get a new key which react "thinks" its a new element thus re-mount the element and you loose the focus

Read more about keys

let test = this.state.values.map( (v, i) => {
      return <input
        key={ v }
        value={ v }
        onChange={ (e) => {
          let values = this.state.values;
          values[i] = e.target.value;
          this.setState({ values });
        } }
      />;
    } )

Not sure if you can change the state structure, but i would go for a different approach and create a form object and give the name and key attributes according to the object's key.

Example:

class App extends React.Component {
  state = {
    form: {
      name: "",
      userName: "",
      age: ""
    },
    externalValue: ""
  };

  onFormInputChange = ({ target }) => {
    const { form } = this.state;
    this.setState({
      form: {
        ...form,
        [target.name]: target.value
      }
    });
  };

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

  render() {
    const { form } = this.state;
    let test = Object.entries(form).map(([key, value]) => {
      return (
        <input
          key={key}
          name={key}
          placeholder={key}
          value={value}
          onChange={this.onFormInputChange}
        />
      );
    });

    return (
      <div>
        <form>{test}</form>
        <form>
          <input
            name="externalValue"
            placeholder="externalValue"
            value={this.state.value}
            onChange={this.onInputChange}
          />
        </form>
      </div>
    );
  }
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"/>

Upvotes: 7

Related Questions