问题
I've inherited a react/node/prismic application where we need to update the prismic-reactjs package, which resulted in needing to update a few others- in total, we changed:
prismic-reactjs: 0.2.0 → 1.1.0
react: 15.6.1 → 16.0.0
webpack: 3.12.0 → 4.39.2
react-dom: 15.6.1 → 16.0.0
react-router-dom: 4.1.2 → 5.0.1
extract-text-webpack-plugin (deprecated) → mini-css-extract-plugin
Then removed one use of "withRouter()" because of new error when starting up the local server (but I confirmed in another branch that making that edit alone doesn't produce the symptom below)
Context: we have a personMap that shows a icons for a set of people, and you click one of them to open a PersonMapStory modal, which shows that person's story, then links to everybody else in a list at the bottom. If you scroll down and click one of those links, the story up top is replaced accordingly (with the new person's story), and list below is everyone else. In the code, the button onClick behavior for these bottom links is setActivePerson() to this new person.
New bug: in this new branch, when we click a bottom link it calls setActivePerson() for person #2, but then also calls setActivePerson() for person #1 again! (Back from the original PersonMap li onClick behavior). So the symptom is that you see the first story, scroll down and click a different person's link, but the modal doesn't appear to update at all (though it's really updating twice). In the current/production/working branch it just updates once to the new person and that's that. This new behavior sounds reeeally similar to this onClick propagation issue. I tried adding stopPropagation() into the appropriate methods, but no luck.
Further info: in this branch, it looks like ANY click from inside that modal (even if I click on the story that's displayed up top) calls SetActivePerson() with the original person, from the PersonMap line item onClick behavior, when thisPersonIsQueued is true. (So if I scroll down and click a new person, we SetActivePerson person #2, followed immediately by SetActivePerson person #1, so the modal keeps the same story and never appears to update). In the PersonMap state (viewed in Chrome Developer Tools/Components) there's a queuedUpPatient: typically in this scenario (in the original/production code) queuedUpPatient is null, but in this new branch, queuedUpPatient keeps the value of Person #1. (or has it cleared out and re-set).
Possible clue in the debugger: After we call SetActivePerson for #2 (which is the last step that happens in our regular/production branch), before we see the setActivePerson call for person #1 from it's onClick behavior, the debugger takes me through several methods in react-dom.development.js, starting with the very last line of callCallback(), inside code for the following occasion (see below):
// 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
Other ideas for what could be going on and how I can get this working normally again so we can go ahead with the package upgrades? Here are the two relevant files for the map and the modal (below).
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
回答1:
Update your onClick
in PersonMapStory
to access the event
object:
<button className="person-map-story-other-button" onClick={e => this.setActivePerson(e, person.name)}>
and change the setActivePerson(name)
function in PersonMapStory
:
setActivePerson(event, name) {
this.props.setActivePerson(name)
event.stopPropagation()
}
来源:https://stackoverflow.com/questions/58530473/since-updating-react-dom-and-react-router-dom-packages-excessive-li-onclick-beh