React 事件处理程序未按预期分配模式,评估为 null
React event handler doesn't assign mode as expected, evaluates as null
我的页面如下所示:
单击行的编辑按钮时,组件应该将所选实体设置为该行并将其自身置于“编辑”模式。
但是,当在右上角显示一个框(这里称为 toast 组件)时,在第一次点击时模式被评估为 null
:
代码如下:
import React, { Component } from 'react';
import { DataTable } from 'primereact/datatable';
import { Column } from 'primereact/column';
import { Button } from 'primereact/button';
import { Toast } from 'primereact/toast';
import 'primereact/resources/themes/luna-blue/theme.css';
import 'primereact/resources/primereact.min.css';
import 'primeicons/primeicons.css';
const modes =
{
VIEW: 'VIEW',
ADD: 'ADD',
EDIT: 'EDIT',
REMOVE: 'REMOVE'
}
class TestManager extends Component
{
constructor()
{
super();
let persons = [
{
"id": 3,
"zipCode": "",
"cityName": "",
"streetName": "",
"houseNbr": "",
"firstName": "Ahmed",
"lastName": "Al-Thiab",
"gender": "MALE",
},
{
"id": 4,
"zipCode": "",
"cityName": "",
"streetName": "",
"houseNbr": "",
"firstName": "Stefan",
"lastName": "Antalovics",
"gender": "MALE",
},
{
"id": 20,
"zipCode": "",
"cityName": "",
"streetName": "",
"houseNbr": "",
"firstName": "Christian",
"lastName": "Attina",
"gender": "MALE",
},
{
"id": 15,
"zipCode": "",
"cityName": "",
"streetName": "",
"houseNbr": "",
"firstName": "Filmon",
"lastName": "Berhane",
"gender": "MALE",
},
{
"id": 2,
"zipCode": "64895",
"cityName": "Darmstadt",
"streetName": "",
"houseNbr": "",
"firstName": "Johannes",
"lastName": "Loczewski",
"gender": "MALE",
},
{
"id": 1,
"zipCode": "22880",
"cityName": "Wedel",
"streetName": "Rosengarten",
"houseNbr": "6",
"firstName": "Karsten",
"lastName": "Wutzke",
"gender": "MALE",
}
]
this.state = {entities: persons,
selectedEntity: null,
mode: null};
this.onRowEdit = this.onRowEdit.bind(this);
this.onRowRemove = this.onRowRemove.bind(this);
this.actions = this.actions.bind(this);
}
render()
{
var header = "Person Manager (" + this.state.entities.length + ")"
return (
<div style={{ maxWidth: 1000, marginLeft: "auto", marginRight: "auto", marginTop: 10, marginBottom: 10 }}>
<Toast ref={(el) => this.toast = el} />
<DataTable value={this.state.entities}
header={header}
dataKey="id"
selection={this.state.selectedEntity}
selectionMode="single"
sortField='lastName'
sortOrder={1}
resizableColumns
columnResizeMode="fit"
className="p-datatable-striped">
<Column field="id" header='ID' sortable style={{width:'7.5%'}} />
<Column field="gender" header='Sal.' body={this.salutation} sortable style={{width:'10%'}} />
<Column field='lastName' header='Last Name' sortable style={{width:'15%'}} />
<Column field='firstName' header='First Name' sortable style={{width:'15%'}} />
<Column field='streetName' header='Street' body={this.street} sortable style={{width:'20%'}} />
<Column field='zipCode' header='ZIP' sortable style={{width:'10%'}} />
<Column field='cityName' header='City' sortable style={{width:'10%'}} />
<Column field="actions" body={this.actions} style={{width:'12.5%'}} />
</DataTable>
</div>
);
}
actions(rowData)
{
return (
<>
<div style={{textAlign: "center"}}>
<Button icon="pi pi-pencil"
tooltip="Edit"
onClick={() => this.onRowEdit(rowData)}
className="p-button-sm p-button-raised p-button-rounded p-button-outlined" />
<Button icon="pi pi-trash"
tooltip="Remove"
className="p-button-sm p-button-raised p-button-rounded p-button-outlined"
onClick={() => this.onRowRemove(rowData)}
style={{marginLeft: 5}} />
</div>
</>
);
}
onRowEdit(rowData)
{
this.setState({selectedEntity: rowData});
this.setState({mode: modes.EDIT});
console.log("Last name: " + rowData.lastName);
console.log("Mode: " + this.state.mode);
this.toast.show({ severity: 'info', summary: 'Editing Person', detail: 'Name: ' + rowData.lastName + ", " + this.state.mode, life: 3000 });
}
onRowRemove(rowData)
{
this.setState({selectedEntity: rowData});
this.setState({mode: modes.REMOVE});
this.toast.show({ severity: 'info', summary: 'Removing Person', detail: 'Name: ' + rowData.lastName + ", " + this.state.mode, life: 3000 });
}
salutation(rowData)
{
var gender = rowData['gender'];
if ( gender )
{
switch( gender )
{
case "MALE":
return "Mr";
case "FEMALE":
return "Mrs";
default:
return "Error";
}
}
return null;
}
street(rowData)
{
var streetName = rowData['streetName'];
var houseNumber = rowData['houseNbr'];
return houseNumber ? streetName + " " + houseNumber : streetName;
}
}
export default TestManager;
问题:
这是怎么回事?
据此
方法回调应该没有任何问题,a.k.a。作为事件处理程序,更新模式
onRowEdit(rowData)
{
this.setState({selectedEntity: rowData});
this.setState({mode: modes.EDIT});
console.log("Last name: " + rowData.lastName);
console.log("Mode: " + this.state.mode);
this.toast.show({ severity: 'info', summary: 'Editing Person', detail: 'Name: ' + rowData.lastName + ", " + this.state.mode, life: 3000 });
}
this.state.mode
在第一次点击时总是 null
。在随后的点击中,无论哪一行,都会显示正确的模式。
为什么?
我该如何解决这个问题?
顺便说一句:使用 toast 的行上的 this.state.selectedEntity
也是 null
。
我无法理解这一点。 -> 自学者
setState
是异步的。你不能指望你的状态已经改变了。
试试这个:
this.setState({mode: modes.EDIT}, () => {
console.log("Mode: " + this.state.mode);
});
在下一次渲染之前,状态更新不会(必然)反映在 this.state
中。
来自the docs:
setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall. Instead, use componentDidUpdate or a setState callback
您已经知道模式,因此您可以传递 modes.EDIT
而不是 this.state.mode
:
this.toast.show({ detail: "..." + modes.EDIT, life: 3000 });
或者使用 setState 的回调形式,确保在调用后续的 toast 代码之前状态已经更新:
this.setState({mode: modes.EDIT}, () => this.toast.show( ... ))
注意:您的 console.log
调用可能会显示更新后的值,因为 console.log 是异步运行的。 (在您调用它的时间和它出现在控制台中的时间之间,状态可能已经改变。)
好吧,这是一个公平的问题,但 setState 是异步的。因此,要么在其中使用回调函数,要么使用 Promise 在另一个 setState
之后执行一些 setState
this.setState( (mode) => mode : modes.EDIT)
或
Promise.all(#First Async).then(#Second Async)
我的页面如下所示:
单击行的编辑按钮时,组件应该将所选实体设置为该行并将其自身置于“编辑”模式。
但是,当在右上角显示一个框(这里称为 toast 组件)时,在第一次点击时模式被评估为 null
:
代码如下:
import React, { Component } from 'react';
import { DataTable } from 'primereact/datatable';
import { Column } from 'primereact/column';
import { Button } from 'primereact/button';
import { Toast } from 'primereact/toast';
import 'primereact/resources/themes/luna-blue/theme.css';
import 'primereact/resources/primereact.min.css';
import 'primeicons/primeicons.css';
const modes =
{
VIEW: 'VIEW',
ADD: 'ADD',
EDIT: 'EDIT',
REMOVE: 'REMOVE'
}
class TestManager extends Component
{
constructor()
{
super();
let persons = [
{
"id": 3,
"zipCode": "",
"cityName": "",
"streetName": "",
"houseNbr": "",
"firstName": "Ahmed",
"lastName": "Al-Thiab",
"gender": "MALE",
},
{
"id": 4,
"zipCode": "",
"cityName": "",
"streetName": "",
"houseNbr": "",
"firstName": "Stefan",
"lastName": "Antalovics",
"gender": "MALE",
},
{
"id": 20,
"zipCode": "",
"cityName": "",
"streetName": "",
"houseNbr": "",
"firstName": "Christian",
"lastName": "Attina",
"gender": "MALE",
},
{
"id": 15,
"zipCode": "",
"cityName": "",
"streetName": "",
"houseNbr": "",
"firstName": "Filmon",
"lastName": "Berhane",
"gender": "MALE",
},
{
"id": 2,
"zipCode": "64895",
"cityName": "Darmstadt",
"streetName": "",
"houseNbr": "",
"firstName": "Johannes",
"lastName": "Loczewski",
"gender": "MALE",
},
{
"id": 1,
"zipCode": "22880",
"cityName": "Wedel",
"streetName": "Rosengarten",
"houseNbr": "6",
"firstName": "Karsten",
"lastName": "Wutzke",
"gender": "MALE",
}
]
this.state = {entities: persons,
selectedEntity: null,
mode: null};
this.onRowEdit = this.onRowEdit.bind(this);
this.onRowRemove = this.onRowRemove.bind(this);
this.actions = this.actions.bind(this);
}
render()
{
var header = "Person Manager (" + this.state.entities.length + ")"
return (
<div style={{ maxWidth: 1000, marginLeft: "auto", marginRight: "auto", marginTop: 10, marginBottom: 10 }}>
<Toast ref={(el) => this.toast = el} />
<DataTable value={this.state.entities}
header={header}
dataKey="id"
selection={this.state.selectedEntity}
selectionMode="single"
sortField='lastName'
sortOrder={1}
resizableColumns
columnResizeMode="fit"
className="p-datatable-striped">
<Column field="id" header='ID' sortable style={{width:'7.5%'}} />
<Column field="gender" header='Sal.' body={this.salutation} sortable style={{width:'10%'}} />
<Column field='lastName' header='Last Name' sortable style={{width:'15%'}} />
<Column field='firstName' header='First Name' sortable style={{width:'15%'}} />
<Column field='streetName' header='Street' body={this.street} sortable style={{width:'20%'}} />
<Column field='zipCode' header='ZIP' sortable style={{width:'10%'}} />
<Column field='cityName' header='City' sortable style={{width:'10%'}} />
<Column field="actions" body={this.actions} style={{width:'12.5%'}} />
</DataTable>
</div>
);
}
actions(rowData)
{
return (
<>
<div style={{textAlign: "center"}}>
<Button icon="pi pi-pencil"
tooltip="Edit"
onClick={() => this.onRowEdit(rowData)}
className="p-button-sm p-button-raised p-button-rounded p-button-outlined" />
<Button icon="pi pi-trash"
tooltip="Remove"
className="p-button-sm p-button-raised p-button-rounded p-button-outlined"
onClick={() => this.onRowRemove(rowData)}
style={{marginLeft: 5}} />
</div>
</>
);
}
onRowEdit(rowData)
{
this.setState({selectedEntity: rowData});
this.setState({mode: modes.EDIT});
console.log("Last name: " + rowData.lastName);
console.log("Mode: " + this.state.mode);
this.toast.show({ severity: 'info', summary: 'Editing Person', detail: 'Name: ' + rowData.lastName + ", " + this.state.mode, life: 3000 });
}
onRowRemove(rowData)
{
this.setState({selectedEntity: rowData});
this.setState({mode: modes.REMOVE});
this.toast.show({ severity: 'info', summary: 'Removing Person', detail: 'Name: ' + rowData.lastName + ", " + this.state.mode, life: 3000 });
}
salutation(rowData)
{
var gender = rowData['gender'];
if ( gender )
{
switch( gender )
{
case "MALE":
return "Mr";
case "FEMALE":
return "Mrs";
default:
return "Error";
}
}
return null;
}
street(rowData)
{
var streetName = rowData['streetName'];
var houseNumber = rowData['houseNbr'];
return houseNumber ? streetName + " " + houseNumber : streetName;
}
}
export default TestManager;
问题:
这是怎么回事?
据此
方法回调应该没有任何问题,a.k.a。作为事件处理程序,更新模式
onRowEdit(rowData)
{
this.setState({selectedEntity: rowData});
this.setState({mode: modes.EDIT});
console.log("Last name: " + rowData.lastName);
console.log("Mode: " + this.state.mode);
this.toast.show({ severity: 'info', summary: 'Editing Person', detail: 'Name: ' + rowData.lastName + ", " + this.state.mode, life: 3000 });
}
this.state.mode
在第一次点击时总是 null
。在随后的点击中,无论哪一行,都会显示正确的模式。
为什么?
我该如何解决这个问题?
顺便说一句:使用 toast 的行上的 this.state.selectedEntity
也是 null
。
我无法理解这一点。 -> 自学者
setState
是异步的。你不能指望你的状态已经改变了。
试试这个:
this.setState({mode: modes.EDIT}, () => {
console.log("Mode: " + this.state.mode);
});
在下一次渲染之前,状态更新不会(必然)反映在 this.state
中。
来自the docs:
setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall. Instead, use componentDidUpdate or a setState callback
您已经知道模式,因此您可以传递 modes.EDIT
而不是 this.state.mode
:
this.toast.show({ detail: "..." + modes.EDIT, life: 3000 });
或者使用 setState 的回调形式,确保在调用后续的 toast 代码之前状态已经更新:
this.setState({mode: modes.EDIT}, () => this.toast.show( ... ))
注意:您的 console.log
调用可能会显示更新后的值,因为 console.log 是异步运行的。 (在您调用它的时间和它出现在控制台中的时间之间,状态可能已经改变。)
好吧,这是一个公平的问题,但 setState 是异步的。因此,要么在其中使用回调函数,要么使用 Promise 在另一个 setState
之后执行一些 setStatethis.setState( (mode) => mode : modes.EDIT)
或
Promise.all(#First Async).then(#Second Async)