由于更新了 react-dom 和 react-router-dom 包:过多的 li onClick 行为
Since updating react-dom and react-router-dom packages: excessive li onClick behavior
我继承了一个 react/node/prismic 应用程序,我们需要更新 prismic-reactjs 包,这导致需要更新其他几个 - 总共,我们改变了:
prismic-reactjs: 0.2.0 → 1.1.0
反应:15.6.1 → 16.0.0
webpack: 3.12.0 → 4.39.2
反应-dom:15.6.1 → 16.0.0
反应路由器-dom: 4.1.2 → 5.0.1
extract-text-webpack-plugin(已弃用)→ mini-css-extract-plugin
然后删除了 "withRouter()" 的一次使用,因为启动本地服务器时出现新错误(但我在另一个分支中确认单独进行该编辑不会产生以下症状)
Context:我们有一个 personMap 显示一组人的图标,您单击其中一个打开 PersonMapStory 模式,显示该人的故事,然后link 发送给底部列表中的其他所有人。如果您向下滚动并单击其中一个 link,顶部的故事会相应地被替换(用新人的故事),下面列出的是其他人。在代码中,这些底部 links 的按钮 onClick 行为是针对这个新人的 setActivePerson() 。
新错误:在这个新分支中,当我们单击底部时 link 它会为第 2 个人调用 setActivePerson(),但随后也会调用 setActivePerson()再次为第一个人! (从原始的 PersonMap li onClick 行为返回)。所以症状是你看到第一个故事,向下滚动并单击另一个人的 link,但模式似乎根本没有更新(尽管它确实更新了两次)。在 current/production/working 分支中,它只对新人更新一次,仅此而已。这种新行为听起来非常类似于 onClick 传播问题。我尝试将 stopPropagation() 添加到适当的方法中,但没有成功。
更多信息:在这个分支中,看起来从该模式内部的任何点击(即使我点击显示在顶部的故事)调用 SetActivePerson() 与原始人,来自 PersonMap 行项目的 onClick 行为,当 thisPersonIsQueued 为真时。 (因此,如果我向下滚动并单击一个新人,我们将设置 SetActivePerson 人 #2,紧接着是 SetActivePerson 人 #1,因此模态保持相同的故事并且永远不会更新)。在 PersonMap 状态(在 Chrome Developer Tools/Components 中查看)有一个 queuedUpPatient:通常在这种情况下(在 original/production 代码中)queuedUpPatient 为 null,但在这个新分支中,queuedUpPatient 保持第一个人的价值。 (或已将其清除并重新设置)。
调试器中的可能线索:在我们为#2 调用 SetActivePerson 之后(这是我们 regular/production 分支中发生的最后一步),在我们看到setActivePerson 从它的 onClick 行为中调用 person #1,调试器带我通过 react-dom.development.js 中的几种方法,从 callCallback() 的最后一行开始,在以下情况的内部代码中(见下文):
// Check that the browser supports the APIs we need to implement our special
// DEV version of invokeGuardedCallback
if (typeof window !== 'undefined' && typeof window.dispatchEvent === 'function' && typeof document
关于可能发生的事情以及如何使它再次正常工作以便我们继续进行软件包升级的其他想法?这是地图和模态的两个相关文件(如下)。
PersonMap.js:
import Modal from 'react-modal'
import PropTypes from 'prop-types'
import React from 'react'
import PersonMapPoint from './PersonMapPoint'
import PersonMapStory from './PersonMapStory'
import PersonMapCallout from './PersonMapCallout'
import PersonMapLocator from './PersonMapLocator'
import PersonMapBackground from './PersonMapBackground'
const CUSTOM_STYLES = {
content: {
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: '#fff',
zIndex: 10,
border: 'none'
}
}
class PersonMap extends React.Component {
constructor(props) {
super(props)
this.setActivePerson = this.setActivePerson.bind(this)
this.setNoActivePerson = this.setNoActivePerson.bind(this)
this.setQueuedUpPerson = this.setQueuedUpPerson.bind(this)
this.setNoQueuedUpPerson = this.setNoQueuedUpPerson.bind(this)
this.checkBlurEvent = this.checkBlurEvent.bind(this)
this.setIsDesktop = this.setIsDesktop.bind(this)
this.checkHasBeenScrolledTo = this.checkHasBeenScrolledTo.bind(this)
this.setTopRef = this.setTopRef.bind(this)
this.handleKeyPress = this.handleKeyPress.bind(this)
this.state = {
activePerson: null,
queuedUpPerson: null,
scrollPos: 0,
isDesktop: false,
hasBeenScrolledTo: false,
lastQueuedPerson: null
}
}
componentDidMount() {
this.setIsDesktop()
this.checkHasBeenScrolledTo()
window.addEventListener('resize', this.setIsDesktop)
window.addEventListener('scroll', this.checkHasBeenScrolledTo)
}
componentWillUnmount() {
window.removeEventListener('resize', this.setIsDesktop)
window.removeEventListener('scroll', this.checkHasBeenScrolledTo)
}
setTopRef(element) {
this.topRef = element
}
setActivePerson(personName) {
this.setState({
activePerson: personName,
scrollPos: window.scrollY
})
event.stopPropagation()
}
setNoActivePerson() {
this.setState({
queuedUpPerson: this.state.activePerson,
activePerson: null
}, () => {
setTimeout(() => {
window.scrollTo(0, this.state.scrollPos)
}, 50)
})
}
setQueuedUpPerson(personName) {
this.setState({
queuedUpPerson: personName,
lastQueuedPerson: personName
})
}
setNoQueuedUpPerson() {
this.setState({
queuedUpPerson: null
})
event.stopPropagation()
}
handleKeyPress(e, name) {
if (e.key !== ' ' && e.key !== 'Enter') {
return
}
this.setActivePerson(name)
}
checkBlurEvent(e) {
if (Array.from(e.currentTarget.childNodes[0].childNodes).includes(e.relatedTarget)) {
return
}
this.setNoQueuedUpPerson()
}
render() {
return (
<section className="slice-area person-map CONSTRAIN">
<div className="person-map-headers">
<div className="person-map-headers-inner">
<h1 className="person-map-title">
{this.props.title}
</h1>
<p className="person-map-disclaimer">
{this.props.disclaimer}
</p>
</div>
</div>
<div
className={`person-map-list-wrap${ this.state.isDesktop ? ' --desktop' : '' }`}
ref={this.state.isDesktop && this.setTopRef}
>
{ this.state.isDesktop &&
<PersonMapBackground isVisible={this.state.hasBeenScrolledTo}/>
}
<ul className="person-map-list">
{
this.props.personStories.map((person) => {
const thisPersonIsQueued = this.state.queuedUpPerson === person.name
const thisPersonIsActive = this.state.activePerson === person.name
const thisPersonWasLastQueued = this.state.lastQueuedPerson === person.name
return (
<li
key={person.name} className={`person-map-list-item${thisPersonWasLastQueued ? ' --active' : ''}`}
onMouseEnter={this.state.isDesktop ? () => this.setQueuedUpPerson(person.name) : null}
onMouseLeave={this.state.isDesktop ? this.setNoQueuedUpPerson : null}
onFocus={this.state.isDesktop ? () => this.setQueuedUpPerson(person.name) : null}
onBlur={this.state.isDesktop ? this.checkBlurEvent : null}
onClick={thisPersonIsQueued || !this.state.isDesktop ? () => this.setActivePerson(person.name) : null}
onKeyPress={(e) => this.handleKeyPress(e, person.name)}
>
{
<PersonMapLocator
x={person.x}
y={person.y}
>
}
<Modal
isOpen={thisPersonIsActive}
onRequestClose={this.setNoActivePerson}
style={CUSTOM_STYLES}
>
<PersonMapStory
name={person.name}
photo={person.photo_url}
story={person.story}
setNoActivePerson={this.setNoActivePerson}
setActivePerson={this.setActivePerson}
isActive={thisPersonIsActive}
otherPersons={this.props.personStories.filter((item) => item.name !== person.name).map((item) => ({ name: item.name, photo: item.photo_url }))}
isDesktop={this.state.isDesktop}
/>
</Modal>
</li>
)
})
}
</ul>
</div>
</section>
)
}
}
PersonMap.propTypes = {
title: PropTypes.string,
disclaimer: PropTypes.string,
personStories: PropTypes.arrayOf(PropTypes.shape({
name: PropTypes.string,
photo_url: PropTypes.string,
x: PropTypes.number,
y: PropTypes.number,
story: PropTypes.shape({
title: PropTypes.string,
link: PropTypes.string,
content: PropTypes.arrayOf(PropTypes.shape({
question: PropTypes.string,
answer: PropTypes.string
}))
})
}))
}
export default PersonMap
PersonMapStory.js:
import PropTypes from 'prop-types'
import React from 'react'
import PersonMapPerson from './PersonMapPerson'
class PersonMapStory extends React.Component {
constructor(props) {
console.log("PersonMapStory constructor")
super(props)
this.deactivateThisPerson = this.deactivateThisPerson.bind(this)
this.check = 0
}
deactivateThisPerson(backPressed = false) {
if (!backPressed) {
history.back()
}
console.log("Set no active person ")
this.props.setNoActivePerson()
}
setActivePerson(name) {
this.props.setActivePerson(name)
event.stopPropagation()
}
componentDidMount() {
const loc = window.location.pathname.substr(1)
this.setState({ location: loc })
const openState = { modalOpen: true }
history.pushState(openState, 'signup-open', loc)
}
render() {
return (
<div className={`person-map-story${this.props.isActive ? ' --active' : ''}${this.props.isDesktop ? ' --desktop' : ''}`}>
<h2 className="person-map-story-title">
{ this.props.story.title }
</h2>
<button className="person-map-story-close-button" onClick={() => {this.deactivateThisPerson(false)}}>
<span className="person-map-story-close-button-text">
Close Story
</span>
</button>
<div className="person-map-story-body">
<span className="person-map-story-person-wrap">
<PersonMapPerson
photo={this.props.photo}
name={this.props.name}
/>
</span>
{
this.props.story.content.map((section) => {
if (section) {
return (
<div key={section.question}>
<h3 className="person-map-story-question">{section.question}</h3>
<p className="person-map-story-answer">
{section.answer}
</p>
</div>
)
}
})
}
{
this.props.story.link &&
<a href={this.props.story.link}
target="_blank"
className="person-map-story-more-link header-and-text-link"
> Read More Stories
</a>
}
<ul className="person-map-story-other-list">
{
this.props.otherPersons.map((person) => (
<li
key={person.name}
className="person-map-story-other-list-item"
>
<button className="person-map-story-other-button" onClick={() => this.setActivePerson(person.name)}>
<PersonMapPerson
name={person.name}
photo={person.photo}
ctaText="View their story"
/>
</button>
</li>
))
}
</ul>
</div>
</div>
)
}
}
PersonMapStory.propTypes = {
name: PropTypes.string,
photo: PropTypes.string,
story: PropTypes.shape({
title: PropTypes.string,
link: PropTypes.string,
content: PropTypes.arrayOf(PropTypes.shape({
question: PropTypes.string,
answer: PropTypes.string
}))
}),
setNoActivePerson: PropTypes.func,
setActivePerson: PropTypes.func,
isActive: PropTypes.bool,
otherPersons: PropTypes.arrayOf(PropTypes.shape({
name: PropTypes.string,
photo: PropTypes.string
})),
isDesktop: PropTypes.bool
}
export default PersonMapStory
更新 PersonMapStory
中的 onClick
以访问 event
对象:
<button className="person-map-story-other-button" onClick={e => this.setActivePerson(e, person.name)}>
并更改 PersonMapStory
中的 setActivePerson(name)
函数:
setActivePerson(event, name) {
this.props.setActivePerson(name)
event.stopPropagation()
}
我继承了一个 react/node/prismic 应用程序,我们需要更新 prismic-reactjs 包,这导致需要更新其他几个 - 总共,我们改变了:
prismic-reactjs: 0.2.0 → 1.1.0
反应:15.6.1 → 16.0.0
webpack: 3.12.0 → 4.39.2
反应-dom:15.6.1 → 16.0.0
反应路由器-dom: 4.1.2 → 5.0.1
extract-text-webpack-plugin(已弃用)→ mini-css-extract-plugin
然后删除了 "withRouter()" 的一次使用,因为启动本地服务器时出现新错误(但我在另一个分支中确认单独进行该编辑不会产生以下症状)
Context:我们有一个 personMap 显示一组人的图标,您单击其中一个打开 PersonMapStory 模式,显示该人的故事,然后link 发送给底部列表中的其他所有人。如果您向下滚动并单击其中一个 link,顶部的故事会相应地被替换(用新人的故事),下面列出的是其他人。在代码中,这些底部 links 的按钮 onClick 行为是针对这个新人的 setActivePerson() 。
新错误:在这个新分支中,当我们单击底部时 link 它会为第 2 个人调用 setActivePerson(),但随后也会调用 setActivePerson()再次为第一个人! (从原始的 PersonMap li onClick 行为返回)。所以症状是你看到第一个故事,向下滚动并单击另一个人的 link,但模式似乎根本没有更新(尽管它确实更新了两次)。在 current/production/working 分支中,它只对新人更新一次,仅此而已。这种新行为听起来非常类似于
更多信息:在这个分支中,看起来从该模式内部的任何点击(即使我点击显示在顶部的故事)调用 SetActivePerson() 与原始人,来自 PersonMap 行项目的 onClick 行为,当 thisPersonIsQueued 为真时。 (因此,如果我向下滚动并单击一个新人,我们将设置 SetActivePerson 人 #2,紧接着是 SetActivePerson 人 #1,因此模态保持相同的故事并且永远不会更新)。在 PersonMap 状态(在 Chrome Developer Tools/Components 中查看)有一个 queuedUpPatient:通常在这种情况下(在 original/production 代码中)queuedUpPatient 为 null,但在这个新分支中,queuedUpPatient 保持第一个人的价值。 (或已将其清除并重新设置)。
调试器中的可能线索:在我们为#2 调用 SetActivePerson 之后(这是我们 regular/production 分支中发生的最后一步),在我们看到setActivePerson 从它的 onClick 行为中调用 person #1,调试器带我通过 react-dom.development.js 中的几种方法,从 callCallback() 的最后一行开始,在以下情况的内部代码中(见下文):
// Check that the browser supports the APIs we need to implement our special
// DEV version of invokeGuardedCallback
if (typeof window !== 'undefined' && typeof window.dispatchEvent === 'function' && typeof document
关于可能发生的事情以及如何使它再次正常工作以便我们继续进行软件包升级的其他想法?这是地图和模态的两个相关文件(如下)。
PersonMap.js:
import Modal from 'react-modal'
import PropTypes from 'prop-types'
import React from 'react'
import PersonMapPoint from './PersonMapPoint'
import PersonMapStory from './PersonMapStory'
import PersonMapCallout from './PersonMapCallout'
import PersonMapLocator from './PersonMapLocator'
import PersonMapBackground from './PersonMapBackground'
const CUSTOM_STYLES = {
content: {
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: '#fff',
zIndex: 10,
border: 'none'
}
}
class PersonMap extends React.Component {
constructor(props) {
super(props)
this.setActivePerson = this.setActivePerson.bind(this)
this.setNoActivePerson = this.setNoActivePerson.bind(this)
this.setQueuedUpPerson = this.setQueuedUpPerson.bind(this)
this.setNoQueuedUpPerson = this.setNoQueuedUpPerson.bind(this)
this.checkBlurEvent = this.checkBlurEvent.bind(this)
this.setIsDesktop = this.setIsDesktop.bind(this)
this.checkHasBeenScrolledTo = this.checkHasBeenScrolledTo.bind(this)
this.setTopRef = this.setTopRef.bind(this)
this.handleKeyPress = this.handleKeyPress.bind(this)
this.state = {
activePerson: null,
queuedUpPerson: null,
scrollPos: 0,
isDesktop: false,
hasBeenScrolledTo: false,
lastQueuedPerson: null
}
}
componentDidMount() {
this.setIsDesktop()
this.checkHasBeenScrolledTo()
window.addEventListener('resize', this.setIsDesktop)
window.addEventListener('scroll', this.checkHasBeenScrolledTo)
}
componentWillUnmount() {
window.removeEventListener('resize', this.setIsDesktop)
window.removeEventListener('scroll', this.checkHasBeenScrolledTo)
}
setTopRef(element) {
this.topRef = element
}
setActivePerson(personName) {
this.setState({
activePerson: personName,
scrollPos: window.scrollY
})
event.stopPropagation()
}
setNoActivePerson() {
this.setState({
queuedUpPerson: this.state.activePerson,
activePerson: null
}, () => {
setTimeout(() => {
window.scrollTo(0, this.state.scrollPos)
}, 50)
})
}
setQueuedUpPerson(personName) {
this.setState({
queuedUpPerson: personName,
lastQueuedPerson: personName
})
}
setNoQueuedUpPerson() {
this.setState({
queuedUpPerson: null
})
event.stopPropagation()
}
handleKeyPress(e, name) {
if (e.key !== ' ' && e.key !== 'Enter') {
return
}
this.setActivePerson(name)
}
checkBlurEvent(e) {
if (Array.from(e.currentTarget.childNodes[0].childNodes).includes(e.relatedTarget)) {
return
}
this.setNoQueuedUpPerson()
}
render() {
return (
<section className="slice-area person-map CONSTRAIN">
<div className="person-map-headers">
<div className="person-map-headers-inner">
<h1 className="person-map-title">
{this.props.title}
</h1>
<p className="person-map-disclaimer">
{this.props.disclaimer}
</p>
</div>
</div>
<div
className={`person-map-list-wrap${ this.state.isDesktop ? ' --desktop' : '' }`}
ref={this.state.isDesktop && this.setTopRef}
>
{ this.state.isDesktop &&
<PersonMapBackground isVisible={this.state.hasBeenScrolledTo}/>
}
<ul className="person-map-list">
{
this.props.personStories.map((person) => {
const thisPersonIsQueued = this.state.queuedUpPerson === person.name
const thisPersonIsActive = this.state.activePerson === person.name
const thisPersonWasLastQueued = this.state.lastQueuedPerson === person.name
return (
<li
key={person.name} className={`person-map-list-item${thisPersonWasLastQueued ? ' --active' : ''}`}
onMouseEnter={this.state.isDesktop ? () => this.setQueuedUpPerson(person.name) : null}
onMouseLeave={this.state.isDesktop ? this.setNoQueuedUpPerson : null}
onFocus={this.state.isDesktop ? () => this.setQueuedUpPerson(person.name) : null}
onBlur={this.state.isDesktop ? this.checkBlurEvent : null}
onClick={thisPersonIsQueued || !this.state.isDesktop ? () => this.setActivePerson(person.name) : null}
onKeyPress={(e) => this.handleKeyPress(e, person.name)}
>
{
<PersonMapLocator
x={person.x}
y={person.y}
>
}
<Modal
isOpen={thisPersonIsActive}
onRequestClose={this.setNoActivePerson}
style={CUSTOM_STYLES}
>
<PersonMapStory
name={person.name}
photo={person.photo_url}
story={person.story}
setNoActivePerson={this.setNoActivePerson}
setActivePerson={this.setActivePerson}
isActive={thisPersonIsActive}
otherPersons={this.props.personStories.filter((item) => item.name !== person.name).map((item) => ({ name: item.name, photo: item.photo_url }))}
isDesktop={this.state.isDesktop}
/>
</Modal>
</li>
)
})
}
</ul>
</div>
</section>
)
}
}
PersonMap.propTypes = {
title: PropTypes.string,
disclaimer: PropTypes.string,
personStories: PropTypes.arrayOf(PropTypes.shape({
name: PropTypes.string,
photo_url: PropTypes.string,
x: PropTypes.number,
y: PropTypes.number,
story: PropTypes.shape({
title: PropTypes.string,
link: PropTypes.string,
content: PropTypes.arrayOf(PropTypes.shape({
question: PropTypes.string,
answer: PropTypes.string
}))
})
}))
}
export default PersonMap
PersonMapStory.js:
import PropTypes from 'prop-types'
import React from 'react'
import PersonMapPerson from './PersonMapPerson'
class PersonMapStory extends React.Component {
constructor(props) {
console.log("PersonMapStory constructor")
super(props)
this.deactivateThisPerson = this.deactivateThisPerson.bind(this)
this.check = 0
}
deactivateThisPerson(backPressed = false) {
if (!backPressed) {
history.back()
}
console.log("Set no active person ")
this.props.setNoActivePerson()
}
setActivePerson(name) {
this.props.setActivePerson(name)
event.stopPropagation()
}
componentDidMount() {
const loc = window.location.pathname.substr(1)
this.setState({ location: loc })
const openState = { modalOpen: true }
history.pushState(openState, 'signup-open', loc)
}
render() {
return (
<div className={`person-map-story${this.props.isActive ? ' --active' : ''}${this.props.isDesktop ? ' --desktop' : ''}`}>
<h2 className="person-map-story-title">
{ this.props.story.title }
</h2>
<button className="person-map-story-close-button" onClick={() => {this.deactivateThisPerson(false)}}>
<span className="person-map-story-close-button-text">
Close Story
</span>
</button>
<div className="person-map-story-body">
<span className="person-map-story-person-wrap">
<PersonMapPerson
photo={this.props.photo}
name={this.props.name}
/>
</span>
{
this.props.story.content.map((section) => {
if (section) {
return (
<div key={section.question}>
<h3 className="person-map-story-question">{section.question}</h3>
<p className="person-map-story-answer">
{section.answer}
</p>
</div>
)
}
})
}
{
this.props.story.link &&
<a href={this.props.story.link}
target="_blank"
className="person-map-story-more-link header-and-text-link"
> Read More Stories
</a>
}
<ul className="person-map-story-other-list">
{
this.props.otherPersons.map((person) => (
<li
key={person.name}
className="person-map-story-other-list-item"
>
<button className="person-map-story-other-button" onClick={() => this.setActivePerson(person.name)}>
<PersonMapPerson
name={person.name}
photo={person.photo}
ctaText="View their story"
/>
</button>
</li>
))
}
</ul>
</div>
</div>
)
}
}
PersonMapStory.propTypes = {
name: PropTypes.string,
photo: PropTypes.string,
story: PropTypes.shape({
title: PropTypes.string,
link: PropTypes.string,
content: PropTypes.arrayOf(PropTypes.shape({
question: PropTypes.string,
answer: PropTypes.string
}))
}),
setNoActivePerson: PropTypes.func,
setActivePerson: PropTypes.func,
isActive: PropTypes.bool,
otherPersons: PropTypes.arrayOf(PropTypes.shape({
name: PropTypes.string,
photo: PropTypes.string
})),
isDesktop: PropTypes.bool
}
export default PersonMapStory
更新 PersonMapStory
中的 onClick
以访问 event
对象:
<button className="person-map-story-other-button" onClick={e => this.setActivePerson(e, person.name)}>
并更改 PersonMapStory
中的 setActivePerson(name)
函数:
setActivePerson(event, name) {
this.props.setActivePerson(name)
event.stopPropagation()
}