Skip to content
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

Merged
merged 5 commits into from
Mar 22, 2018

Conversation

youknowriad
Copy link
Contributor

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

  • Use the Image/Gallery blocks
  • Set, remove, update the featured image
  • It should work like master.

@youknowriad youknowriad self-assigned this Mar 20, 2018
@youknowriad youknowriad requested a review from aduth March 20, 2018 09:19
} );
} );

it( 'yields with requested media', async () => {
Copy link
Contributor Author

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 ) {
Copy link
Contributor Author

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.

Copy link
Member

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 );
Copy link
Contributor Author

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.

Copy link
Member

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 ) {
Copy link
Member

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?

Copy link
Contributor Author

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?

Copy link
Contributor Author

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

Copy link
Member

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 } ) => ( {
Copy link
Member

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' ], {} );
Copy link
Member

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.

Copy link
Contributor Author

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

@@ -297,14 +297,14 @@ export default compose( [
withContext( 'editor' )( ( settings ) => {
return { settings };
} ),
withAPIData( ( props ) => {
withSelect( ( select, props ) => {
const { id } = props.attributes;
if ( ! id ) {
return {};
Copy link
Member

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 /> }
Copy link
Member

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".

Copy link
Contributor Author

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

let state = deepFreeze( {
media: {},
} );
expect( getMedia( state, 1 ) ).toBe( undefined );
Copy link
Member

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)

@@ -37,6 +42,19 @@ export function terms( state = {}, action ) {
return state;
}

export function media( state = {}, action ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DocBlock 😄

aduth
aduth previously requested changes Mar 21, 2018
@@ -75,10 +76,10 @@ class GalleryImage extends Component {
}

componentWillReceiveProps( { isSelected, image } ) {
if ( image && image.data && ! this.props.url ) {
if ( image && ! this.props.url ) {
Copy link
Member

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,
Copy link
Member

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).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Damn copy/paste

@youknowriad youknowriad merged commit 89205a9 into master Mar 22, 2018
@youknowriad youknowriad deleted the update/media-fetching branch March 22, 2018 08:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants