Three ways to cross-component distribution in the state of React

Three ways to cross-component distribution in the state of React

When I asked myself for the hundredth time, I'm working on a typical CRUD screen: "Should I remain in this state assemblies or move it to the parent component?."

If desired sub-assembly state of the control light. You may also experience the same problem.

Let's review it through a simple example and three restorative methods. The first two methods is common practice, the third method is not conventional.

problem;

In order to show you what I mean, I'll use a simple books CRUD (Translator's Note: Increase (Create), read queries (Retrieve), update (Update) and delete (Delete)) screen (so simple, it does not create and delete operations).

clipboard.png

We have three components. <BookList /> is a component that displays a list of books and buttons for editing thereof. <BookForm /> has two inputs and a button to save the changes to books. And the other comprising two components <BookApp />.

So, what is our state? Well, <BookApp /> should track and identify the contents of a list of books currently being edited books. <BookList /> no state. And <BookForm /> should maintain the current state of the inputs until you click the "Save" button.

import React, { Component } from "react";
import { render } from "react-dom";

const books = [
  {
    title: "The End of Eternity",
    author: "Isaac Asimov"
  },
  //...
];

const BookList = ({ books, onEdit }) =&gt; (
  <table>
    <tr>
      <th>Book Title</th>
      <th>Actions</th>
    </tr>
    {books.map((book, index) =&gt; (
      <tr>
        <td>{book.title}</td>
        <td>
          &lt;button onClick={() =&gt; onEdit(index)}&gt;Edit&lt;/button&gt;
        </td>
      </tr>
    ))}
  </table>
);

class BookForm extends Component {
  state = { ...this.props.book };
  render() {
    if (!this.props.book) return null;
    return (
      &lt;form&gt;
        <h3>Book</h3>
        &lt;label&gt;
          Title:
          &lt;input
            value={this.state.title}
            onChange={e =&gt; this.setState({ title: e.target.value })}
          /&gt;
        &lt;/label&gt;
        &lt;label&gt;
          Author:
          &lt;input
            value={this.state.author}
            onChange={e =&gt; this.setState({ author: e.target.value })}
          /&gt;
        &lt;/label&gt;
        &lt;button onClick={() =&gt; this.props.onSave({ ...this.state })}&gt;
          Save
        &lt;/button&gt;
      &lt;/form&gt;
    );
  }
}

class BookApp extends Component {
  state = {
    books: books,
    activeIndex: -1
  };
  render() {
    const { books, activeIndex } = this.state;
    const activeBook = books[activeIndex];
    return (
      <div>
        &lt;BookList
          books={books}
          onEdit={index =&gt;
            this.setState({
              activeIndex: index
            })}
        /&gt;
        &lt;BookForm
          book={activeBook}
          onSave={book =&gt;
            this.setState({
              books: Object.assign([...books], { [activeIndex]: book }),
              activeIndex: -1
            })}
        /&gt;
      </div>
    );
  }
}

render(&lt;BookApp /&gt;, document.getElementById("root"));

In codesandbox try

Looks good, but he does not work.

Initialized we are creating a component instance <BookForm /> state, therefore, when you select another book from the list, the parent can not let it know it needs to change it.

How do we change to fix it?

Method 1: controlled components

A common approach is to improve the state of the <BookForm /> is converted to the controlled components. We removed <BookForm /> state, adding activeBook to <BookApp /> state, and add a onChange props, we will call it every time you enter <BookForm />.

//...

class BookForm extends Component {
  render() {
    if (!this.props.book) return null;
    return (
      &lt;form&gt;
        <h3>Book</h3>
        &lt;label&gt;
          Title:
          &lt;input
            value={this.props.book.title}
            onChange={e =&gt;
              this.props.onChange({
                ...this.props.book,
                title: e.target.value
              })}
          /&gt;
        &lt;/label&gt;
        &lt;label&gt;
          Author:
          &lt;input
            value={this.props.book.author}
            onChange={e =&gt;
              this.props.onChange({
                ...this.props.book,
                author: e.target.value
              })}
          /&gt;
        &lt;/label&gt;
        &lt;button onClick={() =&gt; this.props.onSave()}&gt;Save&lt;/button&gt;
      &lt;/form&gt;
    );
  }
}

class BookApp extends Component {
  state = {
    books: books,
    activeBook: null,
    activeIndex: -1
  };
  render() {
    const { books, activeBook, activeIndex } = this.state;
    return (
      <div>
        &lt;BookList
          books={books}
          onEdit={index =&gt;
            this.setState({
              activeBook: { ...books[index] },
              activeIndex: index
            })}
        /&gt;
        &lt;BookForm
          book={activeBook}
          onChange={book =&gt; this.setState({ activeBook: book })}
          onSave={() =&gt;
            this.setState({
              books: Object.assign([...books], { [activeIndex]: activeBook }),
              activeBook: null,
              activeIndex: -1
            })}
        /&gt;
      </div>
    );
  }
}

//...

Method 2: synchronization state

Now it can work, but for me, lifting <BookForm /> The state does not feel right. Before the user clicks the "Save", <BookApp /> do not care about any changes to the book, then why the need to keep it in their own state?

In codesandbox try

Now it can work, but for me, lifting <BookForm /> The state does not feel right. Before the user clicks the "Save", <BookApp /> do not care about any changes to the book, then why the need to keep it in their own state?

//...
class BookForm extends Component {
  state = { ...this.props.book };
  componentWillReceiveProps(nextProps) {
    const nextBook = nextProps.book;
    if (this.props.book !== nextBook) {
      this.setState({ ...nextBook });
    }
  }
  render() {
    if (!this.props.book) return null;
    return (
      &lt;form&gt;
        <h3>Book</h3>
        &lt;label&gt;
          Title:
          &lt;input
            value={this.state.title}
            onChange={e =&gt; this.setState({ title: e.target.value })}
          /&gt;
        &lt;/label&gt;
        &lt;label&gt;
          Author:
          &lt;input
            value={this.state.author}
            onChange={e =&gt; this.setState({ author: e.target.value })}
          /&gt;
        &lt;/label&gt;
        &lt;button onClick={() =&gt; this.props.onSave({ ...this.state })}&gt;
          Save
        &lt;/button&gt;
      &lt;/form&gt;
    );
  }
}
//...

In codesandbox try

This method is generally considered a bad practice because it violates the React idea of ​​having a single source of truth. I'm not sure this is the case, however, the synchronization status is not always easy. In addition, I try to avoid using the life cycle approach.

Method 3: Components controlled by Key

But why do we want to recycle the old state? Each time the user selects a book, with a new instance of the new state is not meaningful?

To this end, we need to tell React to stop using the old instance and create a new instance. This is the key prop of use.

//...
class BookApp extends Component {
state = {

books: books,
activeIndex: -1

};
render() {

const { books, activeIndex } = this.state;
const activeBook = books[activeIndex];
return (
  <div>
    &lt;BookList
      books={books}
      onEdit={index =&gt;
        this.setState({
          activeIndex: index
        })}
    /&gt;
    &lt;BookForm
      key={activeIndex}
      book={activeBook}
      onSave={book =&gt;
        this.setState({
          books: Object.assign([...books], { [activeIndex]: book }),
          activeIndex: -1
        })}
    /&gt;
  </div>
);

}
}
//...

In codesandbox try.

If the element has rendered on a different key, you React will create a new instance. Thus, when the user selects a new book, <BookForm /> key change creates a new instance of the component, and props from the initialized state.

What's the catch? Examples of reusable components means fewer DOM mutation, which means better performance. Therefore, when we are forced to React to create a new instance of a component, we will get some extra DOM mutation overhead. But for such a situation, this overhead is minimal, in which the key is not changing too fast and small components.

Thank you for reading.

Guess you like

Origin www.cnblogs.com/homehtml/p/11871732.html
Recommended