0

I'm trying to create a dynamic form, in this form users can add as many books as they want, and each book has a name and an link.

I already have a button Add a Book and fields are added correctly in my form, but when I type in any one of these fields my input loses the focus

I'm saving books [] inside of my component state, and to update it I have an onChange={(e) => this.handleDynamicChange(e, 'fieldName', index)} in my input.

handleDynamicChange

handleDynamicChange = (event, name, index) => {
    const { value } = event.target;

    let { books } = this.state;
    books[index][name] = value;
    this.setState({ books });
}

My input for book link (using material ui):

<TextField
    id={`edit-profile-book-link-${index}`}
    label='Link'
    value={book.link}
    onChange={(e) => this.handleDynamicChange(e, 'link', index)}
/>

I guess it's because I'm updating the entire books array, so react is re-rendering all books fields, but I couldn't find a way to update only one book using setState(), so, do you know how to handle these updates inside of a dynamic form ?

I already saw that question but didn't help: React.js - input losing focus when rerendering

I have been checking this tutorial: building a dynamic controlled form

Lucas Andrade
  • 4,315
  • 5
  • 29
  • 50
  • 1
    I'm not sure if this will help, but generally you should have a unique `key` prop on the top level component inside a loop. If your `TextField` is inside a loop, add something like `key={i}` – sliptype Aug 22 '19 at 20:07
  • The problem is not evident from the code that you've posted, it seems to work just fine, here is an example https://codesandbox.io/embed/material-demo-iqdmg – Titus Aug 22 '19 at 20:14
  • Thanks, the key solved my problem, I was already adding a random key, but if the key changes, the input loses the focus. using index it's ok. – Lucas Andrade Aug 22 '19 at 20:14

1 Answers1

1
    let { books } = this.state;
    books[index][name] = value;

Here you are mutating the state, you shouldn't be manipulating the state directly. The recommend way of doing this is to use the spread operator.

this.setState({ books: [...this.state.books,
                   this.state.books[index]: {...this.state.book[index], this.state.books[index][name]: value}
                ] })
user9408899
  • 4,202
  • 3
  • 19
  • 32
  • `books` is an array, and the recommended way is not using *the spread operator* is calling `setState` with a function that receives the previous state as an argument. – Titus Aug 22 '19 at 20:28
  • No, the shortest way is to use the spread operator. You can achive the same thing without it, but it is long. So the recommended way is to use the spread operator. @Titus – user9408899 Aug 22 '19 at 20:31
  • That still won't work, it should be something like this: `this.setState(prevState => ({books: prevState.books.map((book, i) => {if (i === index) {return {...book, [link]: value};} return book;})}))` – Titus Aug 22 '19 at 20:43
  • In this scenario you don't have to use the `prevState`. @Titus – user9408899 Aug 22 '19 at 21:09