简体   繁体   中英

React: update component's props from outside of render method without using state

Here is what I'm trying to achieve. I have two React components Product and ProductInfoPanel , shown inside a ProductList component. Product displays selected information about a product, such as product name, and price. When a product is clicked, more details will be shown in the ProductInfoPanel . So I need to pass wah twas clicked to the ProductInfoPanel .

Here is how I currently wire them up together. Each Product gets a click handler passed in, which passes back the product object when invoked, then that is passed into the ProductInfoPanel 's props. The ProductList uses state to keep track of what was clicked, so when it changes, it triggers the re-rendering of the info panel.

class ProductList extends React.Component {
  render() {
    return (
      <div>

        <div className='content'>

          <ul>
            { this.props.products.map((product, index) => {
              return (
                <li key={index}>
                  <Product product={product}
                    clickHandler={this.onProductClicked.bind(this)}/>
                </li>
              );
            })}
          </ul>

        </div>

        <div className='side-panel'>
          <ProductInfoPanel product={this.state.selectedProduct} />
        </div>

      </div>
    );
  }

  onProductClicked(clickedProduct) {
      // Use the product object that was clicked, and updates the state.
      // This updates the info panel content.
      this.setState({ selectedProduct: clickedProduct });
  }

}

Here is roughly how the two components are constructed.

class Product extends React.Component {
  render() {
   // Even though it needs only name and price, it gets the whole product
   // object passed in so that it can pass it to the info panel in the
   // click handler.
    return (
      <div onClick={this.onClicked.bind(this)}>
        <span>{this.props.product.name}</span>
        <span>{this.props.product.price}</span>
      </div>
    );
  }

  onClicked(e) {
    this.props.clickHandler(this.props.product);
  }
}

class ProductInfoPanel extends React.Component {
  render() {
    // Info panel displays more information about a product.
    return (
      <ul>
        <li>{this.props.product.name}</li>
        <li>{this.props.product.price}</li>
        <li>{this.props.product.description}</li>
        <li>{this.props.product.rating}</li>
        <li>{this.props.product.review}</li>
      </ul>
    );
  }
}

This is the best I could come up with, but using state to keep track of what product was clicked still sounds wrong to me. I mean, it's not really a state of a component, is it?

If I could update props of a referenced React component from outside of the render method, then I'd try to pass a reference to a ProductInfoPanel to each Product , so they could do update it in their click handler.

Is there a way to achieve what I want and avoid using state to keep track of what was clicked?

You could use a flux-like library like redux , or an alternative like mobx to remove state management from your component, but my personal feeling is to keep it as simple as possible until you really feel like there will be significant benefit in adding another layer of abstraction into your project.

I used to start off projects using redux by default but then one time I kicked myself as it turned out that the added complexity of introducing a redux implementation turned out to be overkill for what was actually a fairly small and simple project. I don't know if there is a hard line to know when you should shy away from using standard state and introduce another library to manage it for you, but I have learned that it's probably safest to do it the easiest and simplest way first until you genuinely feel there is actual benefit in bring in another dependency.


A few bits of advice on your current code...

You are binding your functions in the properties like so:

<Product product={product} clickHandler={this.onProductClicked.bind(this)}/>

When you call function bind it actually returns a new function instance, therefore React's reconciler will see it as a new prop coming into your component and will therefore always re-render the subcomponent tree. Something to be aware of. As an alternative approach you can do early binding in your constructor like so:

class ProductList extends React.Component {
  constructor(props) {
    super(props);
    this.onProductClicked = this.onProductClicked.bind(this);
  }
  render() {
    ...
        <li key={index}>
          <Product product={product}
            clickHandler={this.onProductClicked}/>
        </li>
    ...
  }
}

Additionally, where you are providing index as they unique key prop above - you should consider using a unique identifier from your product model (if it's available). That way if you add or remove items from the list React will have more information to know whether or not it should re-render all of the Product component instances.

For example:

render() {
  ...
    { 
      this.props.products.map((product) => 
        <li key={product.id}>
          <Product product={product}
            clickHandler={this.onProductClicked}/>
        </li>
      )
    }

  ...
}

Read more about these concepts here: https://facebook.github.io/react/docs/advanced-performance.html

I think it's fine. If there were more components that responded to changes in SelectedProduct , then the value of having the parent component control the state would be more apparent. In your case, it might not seem necessary, since only a single component changes.

However, if your Product also responded by highlighting the SelectedProduct , and a RecentlyViewedProducts list responded in some way to the SelectedProduct , then it would become evident that the SelectedProduct isn't the state of the ProductInfoPanel , but state of a higher level part of the application that it's an observer of.

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