【发布时间】:2020-03-12 16:40:50
【问题描述】:
我正在编写一个用户可编辑的组件,该组件将用户更改保持在其状态内。我希望能够以 2 种方式使用该组件:1: 一次由作者硬编码,或 2: 从父组件状态的数组生成.在第二种情况下,我无法同步状态。我希望组件是可移除的,所以它有一个“移除我”按钮,它应该通过使用回调函数 prop 与父级的状态进行通信。
场景:
假设我有一个父组件,其状态中有一个数组。从此数组中,子组件使用.map 语句呈现:
// in ParentComponent.js:
state = {
markers: [
{coords: Array(2), popupContent: "Popup 1"},
{coords: Array(2), popupContent: "Popup 2"},
{coords: Array(2), popupContent: "Popup 3"},
... etc ...
]
}
// In the return:
this.state.markers.map( (marker, index) => (
<Marker key={index}>
<Popup sourceKey={index}
setContentCallback={this.saveContentToState}
removalCallback={this.removalCallback} >
{marker.popupContent}
</Popup>
</Marker>
))
有问题的组件是孙子<Popup/>。在 Popup.js 中,用户可以对组件的内容进行更改,这些更改会保存在 Popup 的状态中:
// In Popup.js
state = {
content: this.props.children,
inputValue: this.props.children
}
onEditHandler = {
this.setState({inputValue: e.target.value})
}
saveEdits = () => {
if (this.props.saveContentCallback){
this.props.saveContentCallback(this.state.inputValue, this.props.sourceKey)
}
this.setState({
content: this.state.inputValue,
})
}
removeSource = () => {
if(this.props.removalCallback){
this.props.removalCallback(this.props.sourceKey)
} else {
// internal leaflet function to remove a popup from a map
this.thePopup.leafletElement._source.remove()
}
}
// Within the return function:
return (
<>
<ContentEditableDiv onChange={this.onEditHandler}>
{ this.state.content }
</ContentEditableDiv>
<div onClick={this.saveEdits}>Save</div>
<div onClick={this.removeSource}>Remove me</div>
</>
)
您可以通过saveEdits 函数查看组件如何在上述任何一种情况下保持其自身状态的变化。但是为了与父母的状态交流变化,它使用了道具removalCallback和saveContentCallback。所以,回到ParentComponent.js,
removalCallback = index => {
mapRef.current.leafletElement.closePopup()
this.setState(prevState => {
prevState.markers.splice(index, 1)
return {
markers: prevState.markers
}
})
}
saveContentToState = (content, index) => {
this.setState( prevState => {
const newMarkers = prevState.markers
newMarkers[index].popupContent = content
return {
...this.state.newMarkers
}
})
}
预期行为
当点击弹出窗口上的“删除”按钮时,我希望回调被调用。当回调被调用时,它应该从 ParentComponent 的状态数组 'markers' 中删除该弹出窗口,并且 ParentComponent 应该只使用剩余的标记重新渲染,以及它们关联的popupContent。例如,如果我从这个数组开始:
state = {
markers: [
{coords: Array(2), popupContent: "Popup 1"},
{coords: Array(2), popupContent: "Popup 2"},
{coords: Array(2), popupContent: "Popup 3"},
]
}
然后单击弹出窗口 2 上的 remove me 按钮,我应该得到这个数组:
state = {
markers: [
{coords: Array(2), popupContent: "Popup 1"},
{coords: Array(2), popupContent: "Popup 3"},
]
}
带有两个带有弹出窗口的标记,上面写着“弹出窗口 1”和“弹出窗口 3”。
实际行为 - 错误
所以我确实在 ParentComponent 状态中得到了期望数组,就像我在上面写的那样。但是,弹出窗口的内部状态不合作。当我单击弹出窗口 #2 上的 remove me 按钮时,我得到 2 个弹出窗口,但它们的内容是“弹出窗口 1”和“弹出窗口 2”。当我查看每个 <Popup /> 组件的内部状态时,每个组件的 content 分别是“Popup 1”和“Popup 2”。就好像当ith 弹出窗口被删除时,它的内部状态以某种方式转移到i+1th 弹出窗口,该弹出窗口通过数组中的所有弹出窗口进行传输。
Working Demo of the Problem
这是一个 react-leaflet 项目,但我觉得这是一个 react 状态管理问题。打开代码框的渲染,你会看到 5 个弹出窗口。当您在任何弹出窗口(最后一个除外)上单击“删除我”时,您会看到所有弹出窗口的数字都发生了变化。在 react 开发工具组件选项卡中,您将看到 <Map />(即<ParentComponent>)状态数组正在正确更新。但是查看每个 <EditablePopup /> 内部状态,这些与父 (<Map>) 组件的状态不对应。我知道像state = { content: this.props.something } 这样的东西会导致问题,但我不确定这是否是这种情况的罪魁祸首。
这里出了什么问题?每次removalCallback 或saveContentCallback 触发时,这些<Marker/> 和<Popup/> 组件不应该所有重新渲染,因为它会更新父级的状态并应该触发父级的重新渲染? 我尝试在父组件内的这些回调中添加this.forceUpdate,但没有任何效果。抱歉问了这么长的问题,感谢阅读。
【问题讨论】:
标签: reactjs callback setstate react-leaflet react-state-management