Reactjs 子组件正确删除父组件';s州';s对象条目,但在重新渲染时卸载了错误的子对象

Reactjs 子组件正确删除父组件';s州';s对象条目,但在重新渲染时卸载了错误的子对象,reactjs,rerender,Reactjs,Rerender,我有一个Order组件,它将一个Javascript对象保存在this.state.newItems中,并为每个子对象呈现OrderItem组件 OrderItem还接收回调以对父级状态进行操作 我得到一个行为,比如javascript对象在删除时被正确更新,但卸载了错误的节点 视频: 如您所见,卸载了错误的OrderItem组件 相关代码: export default class Order extends Component { constructor (props) { s

我有一个
Order
组件,它将一个Javascript对象保存在
this.state.newItems
中,并为每个子对象呈现
OrderItem
组件

OrderItem
还接收回调以对父级状态进行操作

我得到一个行为,比如javascript对象在删除时被正确更新,但卸载了错误的节点

视频:

如您所见,卸载了错误的
OrderItem
组件

相关代码:

export default class Order extends Component {
  constructor (props) {
    super (props)

    this.state = {
        newItems: null
    }

    if (this.props.isNew)
        this.data = null
    else {
        this.data = { ... this.props }
    }
  }

  /* _commit = () => {
    console.log(this.data)
    if (this.data.trim().length > 0)
      socket.emit('article:client:insert', { name: this.data })
  } */

  addNewOrderItem = async () => {
    let _ = this.state.newItems ? { ... this.state.newItems } : {}

    _[Date.now().toString()] = {
        articleData: {
          isNew: true,
          unitaryPrice: 0.0
        }
    }

    await this.setState({
        newItems: _
    })

    console.log(this.state.newItems)
  }

  deleteNewOrderItem = async id => {
      let _ = { ...this.state.newItems }

      console.log("Deleting " + id)
      delete _[id]

      await this.setState({
        newItems: _
      })

      console.log(this.state.newItems)
  }

  updateNewOrderItem = async (id, value) => {
      let _ = { ...this.state.newItems }
      _[id] = value

      await this.setState({
        newItems: _
      })

      console.log(this.state.newItems)
  }

  renderNewItems () {
      if (!this.state.newItems) return null

      let _ = []
      for (let _id in this.state.newItems)
          _.push(
            <OrderItem 
                articleData={this.state.newItems[_id].articleData}
                id={_id}
                onUpdate={this.updateNewOrderItem}
                onDelete={this.deleteNewOrderItem}
            />
          )

      return _
  }

  render () {
    const { data } = this

    return (
       // ...
        {
            this.renderNewItems()
        }
       // ...
    )
  }
}


export default class OrderItem extends React.Component {
  state = {
    base64img: null
  }

  constructor (props) {
    super (props)
    this.setData(props)
  }

  componentWillUnmount () {
    console.log("Will unmount " + this.props.id)
  }

  setData (props) {
    this.data = { ...props }
    delete this.data.onDelete
    delete this.data.onUpdate
  }

  handleImageInsert = (event) => {
    let objectFile = event.target.files[0]
    if (!objectFile) return

    let reader = new FileReader()

    reader.onload = upload => 
      this.setState({
        base64img: upload.target.result
      })

    reader.readAsDataURL(objectFile)
    this.data.articleData.newImage = objectFile

    this.updateData()
  }

  handleUnitaryPriceChange = e => {
      let newPrice = parseFloat(e.target.value)
      if (newPrice != NaN) {
        this.data.articleData.unitaryPrice = newPrice
        this.updateData()
      }
  }

  handleBriefChange = e => {
    this.data.articleData.brief = e.target.value
    this.updateData()
  }

  handleTableChange = update => {
    this.data = { ... this.data, ... update }
    this.updateData()
  }

  updateData = () => {
    this.props.onUpdate(this.props.id, this.data)
  }

  confirmDelete = () => {
    this.props.onDelete(this.props.id)
  }

  render () {
    const { data: { articleData } } = this

    return (
      <div>
        <input
          ref={ref => this.fileUploadRef = ref}
          onChange={this.handleImageInsert}
          style={{ display: 'none' }}
          type="file"
          accept="image/*"
        />
        <div className="orderTableImageColumn">
          {
            this.state.base64img &&
            <img src={this.state.base64img} style={{width: '100%'}} />
          }
          <div className="orderTableImageColumnControls">
            <Button
              variant="raised"
              className="print-hide"
              color="primary"
              style={{ display: 'inline-block' }}
              onClick={() => this.fileUploadRef.click()}
            >
              <PhotoCamera />
            </Button>&nbsp;&nbsp;
            <Button
              variant="raised"
              className="print-hide"
              color="primary"
              style={{ display: 'inline-block', backgroundColor: 'red' }}
              onClick={this.confirmDelete}
            >
              <Delete />
            </Button>
          </div>
        </div>
        <div
          style={{
            whiteSpace: 'normal',
            wordWrap: 'break-word',
            width: '65%',
            padding: '2%',
            paddingLeft: '1%',
            verticalAlign: 'top',
            display: 'inline-block'
          }}>
            <div style={{display: 'inline-block', width: '50%'}}>
              {
                !(articleData.isPending || articleData.isNew) && <span className='tableLabelSmall'>SKU</span>
              }

              <br /><br />
              <textarea 
                name="brief"
                className='articleBriefTextarea'
                onChange={this.handleBriefChange}
                value={articleData.brief ? articleData.brief : 'Descrizione'}
                />
            </div>
            <div style={{
              display: 'inline-block', 
              width: '47%',
              marginLeft: '1%',
              padding: '1%',
              verticalAlign: 'top',
              backgroundColor: 'gold'
            }}>
              <span className='tableLabelSmall'>PREZZO UNITARIO €</span>&nbsp;&nbsp;
              <input 
                type="text" 
                style={{padding: '5px', marginBottom: '3px'}} 
                oninput="this.value = this.value.replace(/[^0-9.]/g, ''); this.value = this.value.replace(/(\..*)\./g, '$1');" 
                name="unitaryPrice"
                placeholder='0'
                onChange={this.handleUnitaryPriceChange}
              />
              <br />
              <textarea
                name="needs"
                className='articleBriefTextarea'
                value={articleData.needs ? articleData.needs : 'Materiali, accessori, necessità'}
              />
            </div>
        </div>
      </div>
    )
  }
}
导出默认类顺序扩展组件{
建造师(道具){
超级(道具)
此.state={
newItems:null
}
如果(this.props.isNew)
this.data=null
否则{
this.data={…this.props}
}
}
/*提交=()=>{
console.log(this.data)
if(this.data.trim().length>0)
emit('article:client:insert',{name:this.data})
} */
addNewOrderItem=async()=>{
让{=this.state.newItems?{…this.state.newItems}:{}
_[Date.now().toString()]={
条款数据:{
是的,
统一价格:0.0
}
}
等待这一天({
新增项目:_
})
console.log(this.state.newItems)
}
deleteNewOrderItem=异步id=>{
让{…this.state.newItems}
console.log(“删除”+id)
删除[id]
等待这一天({
新增项目:_
})
console.log(this.state.newItems)
}
updateNewOrderItem=异步(id,值)=>{
让{…this.state.newItems}
_[id]=值
等待这一天({
新增项目:_
})
console.log(this.state.newItems)
}
renderNewItems(){
如果(!this.state.newItems)返回null
让我们
for(在此.state.newItems中设置_id)
_.推(
)
返回_
}
渲染(){
const{data}=这个
返回(
// ...
{
this.renderNewItems()
}
// ...
)
}
}
导出默认类OrderItem扩展React.Component{
状态={
base64img:null
}
建造师(道具){
超级(道具)
这个.setData(props)
}
组件将卸载(){
log(“将卸载”+此.props.id)
}
设置数据(道具){
this.data={…props}
删除此.data.onDelete
删除此.data.onUpdate
}
handleImageInsert=(事件)=>{
让objectFile=event.target.files[0]
如果(!objectFile)返回
let reader=new FileReader()
reader.onload=上传=>
这是我的国家({
base64img:upload.target.result
})
reader.readAsDataURL(objectFile)
this.data.articleData.newImage=objectFile
这是updateData()
}
handleUnitaryPriceChange=e=>{
让newPrice=parseFloat(例如target.value)
if(newPrice!=NaN){
this.data.articleData.unitaryPrice=newPrice
这是updateData()
}
}
车把更换=e=>{
this.data.articleData.brief=e.target.value
这是updateData()
}
handleTableChange=更新=>{
this.data={…this.data,…update}
这是updateData()
}
更新数据=()=>{
this.props.onUpdate(this.props.id,this.data)
}
确认删除=()=>{
this.props.onDelete(this.props.id)
}
渲染(){
const{data:{articleData}}=这个
返回(
this.fileUploadRef=ref}
onChange={this.handleImageInsert}
样式={{display:'none'}}
type=“文件”
accept=“image/*”
/>
{
this.state.base64img&&
}
this.fileUploadRef.click()}
>
{
!(articleData.isPending | | articleData.isNew)和&SKU
}


PREZZO UNITARIO€
) } }
您需要在
上设置
道具,以便React知道哪些道具对应于哪个组件实例:

renderNewItems () {
      if (!this.state.newItems) return null

      let _ = []
      for (let _id in this.state.newItems)
          _.push(
            <OrderItem 
                key={_id}
                articleData={this.state.newItems[_id].articleData}
                id={_id}
                onUpdate={this.updateNewOrderItem}
                onDelete={this.deleteNewOrderItem}
            />
          )

      return _
  }
renderNewItems(){
如果(!this.state.newItems)返回null
让我们
for(在此.state.newItems中设置_id)
_.推(
)
返回_
}
控制台中的错误实际上也在警告您这一点


另一方面,如果不允许子组件改变父组件的状态,则更容易对代码进行推理。

您需要将
键设置为
上的
道具,以便React知道哪些道具对应于哪个组件实例:

renderNewItems () {
      if (!this.state.newItems) return null

      let _ = []
      for (let _id in this.state.newItems)
          _.push(
            <OrderItem 
                key={_id}
                articleData={this.state.newItems[_id].articleData}
                id={_id}
                onUpdate={this.updateNewOrderItem}
                onDelete={this.deleteNewOrderItem}
            />
          )

      return _
  }
renderNewItems(){
如果(!this.state.newItems)返回null
让我们
for(在此.state.newItems中设置_id)
_.推(
)
返回_
}
控制台中的错误实际上也在警告您这一点


另一方面,如果不允许子组件改变父组件的状态,那么就更容易对代码进行推理。

为什么数据不存储在状态中?
data
OrderItem
道具的深层副本,因此,在该组件内部是可变的,我可以将其传递回父级的直通注入回调。为什么
数据
不存储在状态中?
数据
订单项
的道具的深度副本,因此该组件内部是可变的,我可以将其传递回父级的直通注入回调