【问题标题】:Is there a way to force a React component to rerender a child?有没有办法强制 React 组件重新渲染孩子?
【发布时间】:2020-07-31 06:56:37
【问题描述】:

我有一个 React 组件 <ProductPrices>:

  render() {
    if (this.props.currentProduct.id !== 'new' && !this.props.currentProductPrices) {
      this.props.fetchProductPrices(this.props.currentProduct.id);
    }

    return (
      <div>
        <div style={{backgroundColor: 'gray', paddingLeft: '10px', paddingTop: '10px', paddingBottom: '5px'}}>
          <h1>Precios</h1>
        </div>
        <div>
          {this.props.currentProductPrices && this.props.currentProductPrices.map((price, index) => {
            console.log({detail: price});
            return (
              <ProductPricesEntry key={price.id} price={price} index={index} />
            )
          })}
        </div>
      </div>
    )
  }

如您所见,&lt;ProductPrices&gt; 包含许多子组件 &lt;ProductPricesEntry&gt;,其数量是动态的,取决于 Redux 状态变量 currentProductPrices

render() {
    console.log({entry: this.props.price});
    return (
      <div style={{display: 'flex', background: 'white', backgroundColor: 'lightgray', marginTop: '2px', paddingLeft: '10px', paddingTop: '10px', paddingBottom: '5px'}}>
        <div className='col-10'>
          <div fullWidth>
            <h3>{this.props.price.prices_table.name}</h3>
          </div>
          <div fullWidth style={{display: 'flex'}}>
            {this.props.price.current_price? 
              <div style={{textAlign: 'right'}} className='col-4'><strong>{currencyFormatter.format(this.props.price.current_price)}</strong></div>:
              <div style={{textAlign: 'right', color: 'orange'}} className='col-4'>Ninguno</div>
            }
            {this.props.price.due_date?
              <div style={{textAlign: 'center'}} className='col-4'><strong>{this.props.price.due_date}</strong></div>:
              <div style={{textAlign: 'center', color: 'orange'}} className='col-4'>Ninguno</div>
            }
            {this.props.price.next_price?
              <div style={{textAlign: 'right'}} className='col-4'><strong>{currencyFormatter.format(this.props.price.next_price)}</strong></div>:
              <div style={{textAlign: 'right', color: 'orange'}} className='col-4'>Ninguno</div>
            }
          </div>
          <div fullWidth style={{display: 'flex'}}>
            <div style={{textAlign: 'right'}} className='col-4'>Actual</div>
            <div style={{textAlign: 'center'}} className='col-4'>Vigencia</div>
            <div style={{textAlign: 'right'}} className='col-4'>Próximo</div>
          </div>
        </div>
        <div className='col-1'>
          <IconButton color="primary" aria-label={''.concat('update-price-', this.props.price.id)}>
            <i className="zmdi zmdi-edit zmdi-hc-fw" onClick={this.handleUpdateClick} />
          </IconButton>
          <Dialog fullWidth open={this.state.updateDialogOpen} arialabelledby={''.concat('update-price-', this.props.price.id)}>
            <DialogTitle id={"".concat("update-price-", this.props.price.id)}>Actualizar Precio</DialogTitle>
            <DialogContentText>
              <div style={{paddingLeft: '25px'}}><h2>{this.props.currentProductData.name}</h2></div>
              <div style={{paddingLeft: '25px'}}><h2>{this.props.price.prices_table.name}</h2></div>
            </DialogContentText>
            <DialogContent>
              <FormControl fullWidth>
                <InputLabel htmlFor="newPrice">Precio</InputLabel>
                <Input
                  type="number"
                  id="newPrice"
                  name="newPrice"
                  value={this.state.newPrice}
                  onChange={this.handleChange}
                  startAdornment={<InputAdornment position="start">$</InputAdornment>}
                />
              </FormControl>
              <div fullWidth><TextField fullWidth label="Fecha" type="date" name="newDate" value={this.state.newDate} onChange={this.handleChange} /></div>
            </DialogContent>
            <DialogActions>
                <Button onClick={this.handleDialogAcceptClick} name="accept" color="primary">
                  Aceptar
                </Button>
                <Button onClick={this.handleDialogCancelClick} name="cancel" color="secondary">
                  Cancelar
                </Button>
              </DialogActions>
          </Dialog>
        </div>
      </div>
    )
  }
}

我在从&lt;ProductPrices&gt; 调用&lt;ProductPricesEntry&gt; 之前放置了console.log() 语句,并在渲染时放置在&lt;ProductPricesEntry&gt; 内部,我可以看到两个console.log() 语句都是第一次到达,但@ 内部的那个如果this.props.currentProductPrices 更改,则无法到达 987654338@:

这是this.props.currentPrices的值不一样,我可以在Redux Tools上看到变化:

问题在于,&lt;ProductPricesEntry&gt; 中的 console.log() 语句永远不会到达,这意味着它不会重新渲染,尽管更改的 Redux 状态值作为道具发送到组件并显示在内部。

我想我做错了什么,但我找不到。

编辑 这是reducer,它改变了必须导致重新渲染的状态:

case UPDATE_PRODUCT_PRICE_SUCCESS: {
      if (state.currentPricesTable) {
        let currentPricesTableProducts = [...state.currentPricesTableProducts];
        let updatedProductIndex = currentPricesTableProducts.findIndex(product => product.id === action.payload.productPrice.id)
        currentPricesTableProducts[updatedProductIndex]['next_price'] = action.payload.productPrice.next_price;
        currentPricesTableProducts[updatedProductIndex]['due_date'] = action.payload.productPrice.start_date;

        return {
          ...state,
          currentPricesTableProducts: [...currentPricesTableProducts],
          alert: {type: ALERT_SUCCESS, message: "El precio se actualizó existosamente."},
          showMessage: true,
        }
      } else if (state.currentProduct) {
        let currentProductPrices = [...state.currentProductPrices];
        let updatedProductPriceIndex = currentProductPrices.findIndex(productPrice => productPrice.prices_table_product === action.payload.productPrice.prices_table_product)

        currentProductPrices[updatedProductPriceIndex].next_price = action.payload.productPrice.next_price;
        currentProductPrices[updatedProductPriceIndex].due_date = action.payload.productPrice.start_date;

        return {
          ...state,
          currentProductPrices: [...currentProductPrices],
          alert: {type: ALERT_SUCCESS, message: "El precio se actualizó existosamente."},
          showMessage: true,
        }
      } else {
        return {
          ...state
        }
      }
    }

如您所见,我将状态变量 currentProductPrices 替换为一个全新的数组。

我在从reducer返回之前添加了一个console.log(),我可以看到数据是正确的。我可以看到变化:

【问题讨论】:

  • currentProductPrices 必须是重新渲染的新数组。如果内部发生了某些变化,则不会导致重新渲染。当您使用地图创建 ProductPricesEntry 元素时,也不要忘记将关键属性添加到它们。
  • @EugeneMankovski 我做了一个版本,请检查一下。
  • 尝试在第二次返回之前将console.log() 放入reducer 中,然后返回更新后的currentProductPrices。还将 key 属性添加到
  • 另外 this.props.fetchProductPrices(this.props.currentProduct.id) 应该在新的 js 周期被调用以不影响渲染。您可以使用 setTimeout 函数或 useEffect 挂钩。
  • @EugeneMankovski 我都做了,但什么也没做。

标签: reactjs


【解决方案1】:

我永远无法让它按原样工作,但我发现问题需要等待,我分享它以防它对任何人有用。

我基本上将&lt;ProductPricesEntry&gt; 连接到 Redux 存储,而不是从父级发送数据作为道具。

class ProductPricesEntry extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      updateDialogOpen: false,
      newPrice: null,
      newDate: null,
      price: null,
    }

    this.price = null;

    this.handleUpdateClick = this.handleUpdateClick.bind(this);
    this.handleDialogAcceptClick = this.handleDialogAcceptClick.bind(this);
    this.handleDialogCancelClick = this.handleDialogCancelClick.bind(this);
    this.handleChange = this.handleChange.bind(this);
  }

  handleUpdateClick = () => {
    this.setState({updateDialogOpen: true});
  }

  handleDialogAcceptClick = () => {
    this.props.updateProductPrice(
      this.price.prices_table_product, 
      this.price.prices_table.id,
      this.props.currentProductData.id,
      this.state.newPrice, this.state.newDate
    );

    let price = {...this.state.price};
    price.due_date = this.state.newDate;
    price.next_price = this.state.newPrice;
    this.setState({newPrice: null, newDate: null, updateDialogOpen: false, price: price});
  }

  handleDialogCancelClick () {
    this.setState({newPrice: null, newDate: null, updateDialogOpen: null});
  }

  handleChange (event) {
    this.setState({[event.target.name]: event.target.value})
  }

  render() {
    this.price = this.props.currentProductPrices[this.props.index]
    return (
      <div key={this.props.index} style={{display: 'flex', background: 'white', backgroundColor: 'lightgray', marginTop: '2px', paddingLeft: '10px', paddingTop: '10px', paddingBottom: '5px'}}>
        <div className='col-10'>
          <div fullWidth>
            <h3>{this.price.prices_table.name}</h3>
          </div>
          <div fullWidth style={{display: 'flex'}}>
            {this.price.current_price? 
              <div style={{textAlign: 'right'}} className='col-4'><strong>{currencyFormatter.format(this.price.current_price)}</strong></div>:
              <div style={{textAlign: 'right', color: 'orange'}} className='col-4'>Ninguno</div>
            }
            {this.price.due_date?
              <div style={{textAlign: 'center'}} className='col-4'><strong>{this.price.due_date}</strong></div>:
              <div style={{textAlign: 'center', color: 'orange'}} className='col-4'>Ninguno</div>
            }
            {this.price.next_price?
              <div style={{textAlign: 'right'}} className='col-4'><strong>{currencyFormatter.format(this.price.next_price)}</strong></div>:
              <div style={{textAlign: 'right', color: 'orange'}} className='col-4'>Ninguno</div>
            }
          </div>
          <div fullWidth style={{display: 'flex'}}>
            <div style={{textAlign: 'right'}} className='col-4'>Actual</div>
            <div style={{textAlign: 'center'}} className='col-4'>Vigencia</div>
            <div style={{textAlign: 'right'}} className='col-4'>Próximo</div>
          </div>
        </div>
        <div className='col-1'>
          <IconButton color="primary" aria-label={''.concat('update-price-', this.price.id)}>
            <i className="zmdi zmdi-edit zmdi-hc-fw" onClick={this.handleUpdateClick} />
          </IconButton>
          <Dialog fullWidth open={this.state.updateDialogOpen} arialabelledby={''.concat('update-price-', this.price.id)}>
            <DialogTitle id={"".concat("update-price-", this.price.id)}>Actualizar Precio</DialogTitle>
            <DialogContentText>
              <div style={{paddingLeft: '25px'}}><h2>{this.props.currentProductData.name}</h2></div>
              <div style={{paddingLeft: '25px'}}><h2>{this.price.prices_table.name}</h2></div>
            </DialogContentText>
            <DialogContent>
              <FormControl fullWidth>
                <InputLabel htmlFor="newPrice">Precio</InputLabel>
                <Input
                  type="number"
                  id="newPrice"
                  name="newPrice"
                  value={this.state.newPrice}
                  onChange={this.handleChange}
                  startAdornment={<InputAdornment position="start">$</InputAdornment>}
                />
              </FormControl>
              <div fullWidth><TextField fullWidth label="Fecha" type="date" name="newDate" value={this.state.newDate} onChange={this.handleChange} /></div>
            </DialogContent>
            <DialogActions>
                <Button onClick={this.handleDialogAcceptClick} name="accept" color="primary">
                  Aceptar
                </Button>
                <Button onClick={this.handleDialogCancelClick} name="cancel" color="secondary">
                  Cancelar
                </Button>
              </DialogActions>
          </Dialog>
        </div>
      </div>
    )
  }
}

const mapStateToProps = ({inventory}) => {
  const {
    currentProduct,
    currentProductData,
    currentProductPrices,
  } = inventory

  return {
    currentProduct,
    currentProductData,
    currentProductPrices,
  }
}

export default connect(mapStateToProps, {updateProductPrice}) (ProductPricesEntry);

现在它可以按预期工作了。

【讨论】:

    猜你喜欢
    • 2023-03-29
    • 2018-12-23
    • 1970-01-01
    • 1970-01-01
    • 2016-08-01
    • 2021-11-18
    • 2018-08-20
    • 2018-05-19
    • 1970-01-01
    相关资源
    最近更新 更多