子组件正确删除父组件状态的对象条目,但在重新渲染时卸载了错误的子组件



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

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

我得到一种行为,例如删除时正确更新了javascript对象,但是卸载了错误的节点

视频: https://streamable.com/bjesf

如您所见,卸载了错误的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>
)
}
}

你需要在<OrderItem />上设置keyprop,以便 React 知道哪些 prop 对应于哪个组件实例:

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 _
}

控制台中的错误实际上也在警告您这一点。

顺便说一句,如果不允许子组件改变父组件的状态,则推理代码会容易得多。

最新更新