-
Notifications
You must be signed in to change notification settings - Fork 4.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Data: Include more details when shallow equality fails in 'useSelect' #67713
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,6 +19,23 @@ import useAsyncMode from '../async-mode-provider/use-async-mode'; | |
|
||
const renderQueue = createQueue(); | ||
|
||
function warnOnUnstableReference( a, b ) { | ||
let keys = []; | ||
if ( a.constructor === Object && b.constructor === Object ) { | ||
keys = Object.keys( a ).filter( ( k ) => a[ k ] !== b[ k ] ); | ||
} else if ( Array.isArray( a ) && Array.isArray( b ) ) { | ||
keys = [ ...a.keys() ].filter( ( i ) => a[ i ] !== b[ i ] ); | ||
} | ||
|
||
// eslint-disable-next-line no-console | ||
console.warn( | ||
'The `useSelect` hook returns different values when called with the same state and parameters.\n' + | ||
'This can lead to unnecessary re-renders and performance issues if not fixed.\n\n' + | ||
'Non-equal value keys: %s\n\n', | ||
keys.join( ', ' ) | ||
); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The warning will be confusing when the It can happen in practice when we write code like this: useSelect( select => {
if ( someCondition ) {
return { loading: false };
}
return { loading: false, data: select( store ).getData() };
} ); To fix this, we could construct a set of all fields on both objects and then filter it: const allKeys = Array.from( new Set( [ ...Object.keys( a ), ...Object.keys( b ) ] ) );
const keys = allKeys.filter( ( k ) => a[ k ] !== b[ k ] ); But this will fail again for objects like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
It means that the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh that's true, I've been overthinking it 🙂 Running the |
||
} | ||
|
||
/** | ||
* @typedef {import('../../types').StoreDescriptor<C>} StoreDescriptor | ||
* @template {import('../../types').AnyConfig} C | ||
|
@@ -159,10 +176,7 @@ function Store( registry, suspense ) { | |
if ( ! didWarnUnstableReference ) { | ||
const secondMapResult = mapSelect( select, registry ); | ||
if ( ! isShallowEqual( mapResult, secondMapResult ) ) { | ||
// eslint-disable-next-line no-console | ||
console.warn( | ||
`The 'useSelect' hook returns different values when called with the same state and parameters. This can lead to unnecessary rerenders.` | ||
); | ||
warnOnUnstableReference( mapResult, secondMapResult ); | ||
didWarnUnstableReference = true; | ||
} | ||
} | ||
|
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.
Object.keys
should work equally well also for arrays. The only difference is that.keys()
returns numbers, whileObject.keys
are strings.The
a.constructor
check will crash whena
isnull
orundefined
, we should guard for that.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.
Yeah. I should have guessed that 😅
I've borrowed the check for
isShallowEqual
, so it should be safe in this case.Based on your suggestion, I think we can simplify logic to the example below. What do you think?
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.
But
isShallowEqual
does anif ( a && b )
check before it starts looking ata.constructor
.typeof null
is also'object'
🙀 It will all work only if you exit early whena
orb
is falsy.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 should stop ignoring the "everything is an object" JS motto 😄
Pushed update in 39bc8c1.