-
Notifications
You must be signed in to change notification settings - Fork 55
fix(EventListener): do not rerender on listener
prop change
#1132
Conversation
static displayName = 'EventListener' | ||
static propTypes = listenerPropTypes | ||
static defaultProps = { | ||
capture: false, | ||
} | ||
|
||
shouldComponentUpdate(nextProps: EventListenerProps) { | ||
return shouldUpdateListener(this.props, nextProps) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This sounds strange, but will work because React will call actual this.props.listener
, added UTs confirm that. This behavior is covered there: https://overreacted.io/a-complete-guide-to-useeffect/#each-render-has-its-own-everything
It will be more clear when we will migrate this logic to hooks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
might refer to another section from the same blog that warns against using shouldUpdate
methods https://overreacted.io/writing-resilient-components/#dont-stop-the-data-flow-in-optimizations :) For example, now, with these changes being made, we should refrain from passing listener
value any further down (the fact that we are not rendering anything helps, but there are no explicit constrains on this component in these regards) - as it will be another source of problems.
At the same time, agree that Hooks policy around when subscription happens (and, surely, around specifying update dependencies) should help in this case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fully agree with you there, however should mention that EventListener
doesn't render any children, so actually we don't stop there nothing.
And yep, hooks will be more obvious with their when 👍
Codecov Report
@@ Coverage Diff @@
## master #1132 +/- ##
==========================================
- Coverage 82.35% 82.35% -0.01%
==========================================
Files 732 733 +1
Lines 8696 8712 +16
Branches 1168 1168
==========================================
+ Hits 7162 7175 +13
- Misses 1519 1522 +3
Partials 15 15
Continue to review full report at Codecov.
|
@@ -0,0 +1,8 @@ | |||
import { EventListenerProps } from '../types' | |||
|
|||
const shouldUpdateListener = (prevProps: EventListenerProps, nextProps: EventListenerProps) => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
actually, the real intent we would like to express here is that "do not update if only listener
was changed". Thus, lets make this intent explicit by providing a logic like the following instead:
const shouldUpdateListener = (prevProps: EventListenerProps, nextProps: EventListenerProps) =>
if (shallowEqual(
{ ...prevProps, listener: nextProps.listener },
nextProps))
{
return false
}
...
This will save us from any kind of maintainability problems that might lead to bugs that would be hard to discover - like, for example, the ones that will appear in case we would need to extend the props of this component
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I support this idea, however I should mention that in this case we will add a new dependency for short term... As we are going to update to React 16.8, may be we can go with existing implementation? However, I am fully agree that your proposal is more understandable...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
agree with your point - lets discuss in person and decide? or, if you feel strongly about merging, please, feel free to skip this comment :) in that case we will be able to handle this in a follow-up PR. The sole reason I am not completely sure to skip that is because shallowEqual
is, essentially, a 10-lines-long func with formatting:
const shallowEqual = (first, second) => {
// or whatever strategy we would have for handling falsy values - here is the most strict one
if (first === undefined) { return second === undefined }
if (first === null) { return second === null; }
if (first === second) { return true; }
if (Object.keys(first).length !== Object.keys(second).length) { return false; }
const isAnyDifference = Object.keys(first).some(
key => first[key] !== second[key]
);
return !isAnyDifference;
};
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed to shallowEqual()
function to this file and remove it fully when we will migrate to hooks
Lets merge the fix with couple of things being addressed: Also, would suggest to provide more details in the issue - specifically, the ones that would explain what really makes it to be an issue. As an example, I might suggest to add the following insights:
This will make it much easier to review the code, as well as to understand the issue itself - otherwise it has taken me a while to follow the same steps you've already made to investigate the issue, as well as, what is more important, to figure out what makes it to be an issue. Thank you! |
Actually, this was mentioned in the original issue, but I got your point about PRs description 👍 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unblocking the fix given we've synced the vision on those points I've expressed my worries about
|
||
it('unsubscribes correctly', () => { | ||
const onClick = jest.fn() | ||
it('will not rerender on change', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand this test.
I would expect that this test was red before the changes and now it is green and helps us avoid any regression.
But is is green even before the change so what's its purpose?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We agreed with @kuzhelov to remove this test as it doesn't save us from regressions
…m/stardust-ui/react into fix/event-listener # Conflicts: # CHANGELOG.md
Fixes #1131.