-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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 Module: Refactor media fetching to use the core data module #5707
Conversation
} ); | ||
} ); | ||
|
||
it( 'yields with requested media', async () => { |
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 really like these tests. Nice work setting up the pattern @aduth
It also shows that we could push it further by avoiding the mocks entirely (using call
effect) but granted that it's not that important at the moment.
* @param {Object} state State tree | ||
* @param {number} id Media id | ||
*/ | ||
export async function* getMedia( state, id ) { |
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 was wondering if passing the state
to the resolvers is a good idea? Maybe it's useless, since we can still call select
from inside the resolver if needed. Kept it as is for now, but let's see how this goes.
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.
Indeed, I'm not too sure how much we want to promote the use of select
, particularly in a context where we know we could call selectors via direct import and pass the state. Another reason was merely to align the signatures between selectors and resolvers (since their names already match).
See also: #5219 (comment)
*/ | ||
export async function* getMedia( state, id ) { | ||
const media = await apiRequest( { path: `/wp/v2/media/${ id }` } ); | ||
yield receiveMedia( media ); |
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'm not tracking any "is requesting" state at the moment because it's not really needed for our current use-cases. But I was wondering if we should have a separate requests
state tree to keep track in a generic way of these states regardless of the request data type, query, ...
Same here, I guess we'll see with use-cases while adding other resolvers.
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 I was wondering if we should have a separate requests state tree to keep track in a generic way of these states regardless of the request data type, query, ...
The original implementation in ffb0760 had a standalone requested
state, which was meant to be generic and may still be an option to explore. The idea of not having a dedicated state works particularly well when we adopt a pattern of a null
value meaning that it's known to exist, but not yet having received the entities. Though this works only with collections. With singular entities, a null
value may instead mean having received an empty entity (404?). I agree we'll have to see how it plays out.
Related: #5219 (comment)
@@ -75,10 +76,10 @@ class GalleryImage extends Component { | |||
} | |||
|
|||
componentWillReceiveProps( { isSelected, image } ) { | |||
if ( image && image.data && ! this.props.url ) { | |||
if ( image && ! this.props.url ) { |
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.
When will image
be falsey?
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.
Before the API loads the image?
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.
Oh I didn't see the object fallback
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.
Not related to this pull request, but why do we access this.props.url
here? Should this not be from the nextProps
argument?
@@ -147,6 +148,6 @@ class GalleryImage extends Component { | |||
} | |||
} | |||
|
|||
export default withAPIData( ( { id } ) => ( { | |||
image: id ? `/wp/v2/media/${ id }` : {}, | |||
export default withSelect( ( select, { id } ) => ( { |
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.
Minor: This works well for an in-place replacement, but in my own experience I want to start going out of my way to be more verbose with how we use withSelect
, to set a good precedent especially when scaling a component to select many things from the same store (example), and to avoid both (a) in-place ownProps
destructuring (destructuring obscures what is being destructured when used in arguments) and (b) the arrow function implicit return of objects (difficult to discern between implicit return and a function body).
This is how I would write:
export default withSelect( ( select, ownProps ) => {
const { getMedia } = select( 'core' );
const { id } = ownProps;
return {
image: id ? getMedia( id ) : null,
};
} )( GalleryImage );
Or:
export default withSelect( ( select, ownProps ) => {
const { getMedia } = select( 'core' );
const { id } = ownProps;
if ( ! id ) {
return;
}
return {
image: getMedia( id ),
};
} )( GalleryImage );
@@ -136,7 +136,7 @@ class ImageBlock extends Component { | |||
} | |||
|
|||
getAvailableSizes() { | |||
return get( this.props.image, [ 'data', 'media_details', 'sizes' ], {} ); | |||
return get( this.props.image, [ 'media_details', 'sizes' ], {} ); |
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.
Observing that we're still pretty bound to the REST API structure of an entity with the new core-data
module. This has its upsides (easy state insertion, consistency), but also doesn't help decouple from the REST API.
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, I was wondering if we should introduce "models" somehow
blocks/library/image/block.js
Outdated
@@ -297,14 +297,14 @@ export default compose( [ | |||
withContext( 'editor' )( ( settings ) => { | |||
return { settings }; | |||
} ), | |||
withAPIData( ( props ) => { | |||
withSelect( ( select, props ) => { | |||
const { id } = props.attributes; | |||
if ( ! id ) { | |||
return {}; |
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.
Minor: With #5421, we don't need to return an object.
</ResponsiveWrapper> | ||
} | ||
{ media && media.isLoading && <Spinner /> } | ||
{ ! media && <Spinner /> } |
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.
What if a media request returns 404 ? This is a concern with treating "empty" as equivalent to "request in progress".
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 think I'm fine with this right now, we can revisit once/if we introduce request state
core-data/test/selectors.js
Outdated
let state = deepFreeze( { | ||
media: {}, | ||
} ); | ||
expect( getMedia( state, 1 ) ).toBe( undefined ); |
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.
Minor: Seems like a separate test case to me (it should return undefined for unknown media
)
core-data/reducer.js
Outdated
@@ -37,6 +42,19 @@ export function terms( state = {}, action ) { | |||
return state; | |||
} | |||
|
|||
export function media( state = {}, action ) { |
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.
DocBlock 😄
@@ -75,10 +76,10 @@ class GalleryImage extends Component { | |||
} | |||
|
|||
componentWillReceiveProps( { isSelected, image } ) { | |||
if ( image && image.data && ! this.props.url ) { | |||
if ( image && ! this.props.url ) { |
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.
Not related to this pull request, but why do we access this.props.url
here? Should this not be from the nextProps
argument?
const { getMedia } = select( 'core' ); | ||
|
||
return { | ||
image: featuredImageId ? getMedia( featuredImageId ) : null, |
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.
You've changed the prop name from media
to image
in the mapping function, and it's resulting in the featured image never being displayed (the underlying component expects a media
prop).
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.
Damn copy/paste
Follow up to #5219
The idea is to leverage the resolver API to replace the usage of the
withAPIData
HoC by the core data module. In this PR, I'm adding support to media fetching to the core-data module.Testing instructions