-
Notifications
You must be signed in to change notification settings - Fork 4.1k
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
Refactor withViewportMatch to use useViewportMatch #18950
Changes from all commits
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 |
---|---|---|
|
@@ -6,8 +6,7 @@ import { mapValues } from 'lodash'; | |
/** | ||
* WordPress dependencies | ||
*/ | ||
import { createHigherOrderComponent } from '@wordpress/compose'; | ||
import { withSelect } from '@wordpress/data'; | ||
import { createHigherOrderComponent, pure, useViewportMatch } from '@wordpress/compose'; | ||
|
||
/** | ||
* Higher-order component creator, creating a new component which renders with | ||
|
@@ -32,13 +31,33 @@ import { withSelect } from '@wordpress/data'; | |
* | ||
* @return {Function} Higher-order component. | ||
*/ | ||
const withViewportMatch = ( queries ) => createHigherOrderComponent( | ||
withSelect( ( select ) => { | ||
return mapValues( queries, ( query ) => { | ||
return select( 'core/viewport' ).isViewportMatch( query ); | ||
} ); | ||
} ), | ||
'withViewportMatch' | ||
); | ||
const withViewportMatch = ( queries ) => { | ||
const useViewPortQueriesResult = () => mapValues( queries, ( query ) => { | ||
let [ operator, breakpointName ] = query.split( ' ' ); | ||
if ( breakpointName === undefined ) { | ||
breakpointName = operator; | ||
operator = '>='; | ||
} | ||
// Hooks should unconditionally execute in the same order, | ||
// we are respecting that as from the static query of the HOC we generate | ||
// a hook that calls other hooks always in the same order (because the query never changes). | ||
// eslint-disable-next-line react-hooks/rules-of-hooks | ||
return useViewportMatch( breakpointName, operator ); | ||
} ); | ||
return createHigherOrderComponent( | ||
( WrappedComponent ) => { | ||
return pure( ( props ) => { | ||
const queriesResult = useViewPortQueriesResult(); | ||
return ( | ||
<WrappedComponent | ||
{ ...props } | ||
{ ...queriesResult } | ||
Comment on lines
+50
to
+54
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.
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. Hi @epiqueras, I don't think it is a concern, we are spreading queries result and passing each prop individually as a boolean e.g: isLarge: false, isSmall: true etc... Even if withViewportMatch is used in a pure component as we are just passing boolean values if useViewportMatch did not change we will not trigger unnecessary rerenders. 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. Right, but 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. Good point, I kept the previous behavior and also used pure HOC as the withSelect does. 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. Nice. |
||
/> | ||
); | ||
} ); | ||
}, | ||
'withViewportMatch' | ||
); | ||
}; | ||
|
||
export default withViewportMatch; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
jest.mock( '@wordpress/compose', () => { | ||
return { | ||
...jest.requireActual( '@wordpress/compose' ), | ||
useViewportMatch: jest.fn(), | ||
}; | ||
} ); |
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.
Interesting, I wonder if there are similar cases/discussions in the React community.
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.
Hi @youknowriad,
I did some research on this. It seems it is possible to do this. The samples people referred to were things like defining an array of states
const useInputs = [...Array(n)].map((_, i) => useState('name' + i));
and they should be avoided because normally there is a better alternative (in that case defined a single state with an array). In this specific case, I don't think we have an alternative (unless we change the hook API), and we are sure that the order the hooks are called will not change during the component life cycle, so it seems like something we could do.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.
Normally, this is symptomatic of a cluttered component tree and means that you should break down the caller into multiple child components, each with their own hook call, but here we are trying to maintain backwards compatibility.
An alternative would be to introduce a
useViewportMatches
hook, but it's not a big deal. We should just clearly document that the query has to be static.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.
Yes, we use this mechanism just to maintain backward compatibility. Given the way an HOC works the query will always be static for a component instance, there is no way of doing the opposite. Changing the query will require a call to the HOC and a new component is created.
I don't think we should change the hook API so I guess we can keep this solution.