-
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
Create withAPIData higher-order component for managing API data #1974
Conversation
Thanks for exploring this. It will be important to streamline communication with the API within blocks for people to build from. I like that it is both explicit and encapsulates concerns fairly well. Compared to usual data-wrappers it also allows more flexibility in what data you are interested in (say, multiple endpoints). How do you see this being used in a block? |
It works well for the block use-case because it's not dependent on a specific Redux store context existing. It can wrap any component; including edit: withApiData( ( props, endpoint ) => {
// ..
} )( ( props ) => {
// ...
} )) |
I may have been a little overzealous with the tagged template literal. It might be possible to recreate the same behavior with a simple array: export default withApiData( ( props ) => ( {
revisions: [ '/wp/v2/posts', props.postId, 'revisions' ]
} ) )( MyPost ); Or export default withApiData( ( props ) => ( {
revisions: [ '/wp/v2/posts/%d/revisions', props.postId ]
} ) )( MyPost ); |
We need something like that. I suspect the WP async data needs will overpass the |
|
||
this.setIntoDataProp( propName, { isLoading: true } ); | ||
|
||
window.fetch( 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.
There are some ongoing discussions to avoid fetch
(to be able to intercept all the API calls). I kind of like fetch
personally.
Interesting idea! I like it. It has some similarities to the
|
I took to breathing life back into this pull request, rebasing to resolve conflicts and moving in a direction away from tagged template literals toward... just simple strings. // Before:
export default withAPIData( ( props, endpoint ) => ( {
post: endpoint`/wp/v2/posts/${ props.postId }`
} ) )( MyPost );
// After:
export default withAPIData( ( props, { type } ) => ( {
post: `/wp/v2/${ type( 'post' ) }/${ props.postId }`
} ) )( MyPost ); Included also is:
Fortunately I was able to make use of the newly extracted |
This is about ready to be merged, but it has me thinking: how do we handle invalidating data? For example, in the included port of We might be able to infer some of this from the server schema (endpoint arguments):
Defining these dependencies in the client could give us some patterns to create optimistic updates (i.e. "what should happen to data if we assume success"). Some prior art includes: Another problem is data freshness, although this isn't as much of an issue until the admin becomes more like a single-page application. Solutions could include time-to-live (TTL) freshness, or lifecycle-based refresh (see Calypso query components). If data dependencies/mutation effects are well defined in the client, this may not be necessary at all. In cases where a component must have fresh data, I think we could extend the mapping value to be an object of settings, optional as an alternative to the string definition: export default withAPIData( ( props, { type } ) => ( {
post: {
path: `/wp/v2/${ type( 'post' ) }/${ props.postId }`,
force: true,
},
} ) )( MyPost ); |
c98ec64
to
04eafe8
Compare
The plugin bundler will not pick up on scripts registered outside this file, and it is simpler to add here than enhance bundler to force reigstration of the shim from compat
Codecov Report
@@ Coverage Diff @@
## master #1974 +/- ##
==========================================
+ Coverage 27.28% 28.65% +1.37%
==========================================
Files 161 165 +4
Lines 4955 5039 +84
Branches 826 830 +4
==========================================
+ Hits 1352 1444 +92
- Misses 3051 3052 +1
+ Partials 552 543 -9
Continue to review full report at Codecov.
|
Several endpoints support pagination, any ideas on how to support pagination in this HoC? |
I touched on pagination in the "Implementation notes" of #2501:
Specifically, paginated endpoints will return response headers which can be used to determine whether there are more results available ( Maybe these are exposed as properties on the mapped prop, e.g. More generally, we might want to track total number of pages regardless of the query parameters of the original request which had initiated it (example). We could get more sophisticated here too using |
Example: class PagedPosts extends Component {
state = {
page: 1
};
onNextPage = ( increment ) => {
const { page } = this.state;
this.setState( { page: page + increment } );
};
render() {
return (
<PagedPostsPage
page={ this.state.page }
onNextPage={ this.onNextPage } />
);
}
}
const PagedPostsPage = withAPIData( ( { page } ) => ( {
posts: `/wp/v2/posts?page=${ page }`
} ) )( ( { posts, onNextPage } ) => (
<div>
{ posts.data && (
<ul>
{ posts.data.map( ( post ) => (
<li>{ post.title.rendered }</li>
) ) }
</ul>
) }
{ page !== 1 && (
<Button onClick={ () => onNextPage( -1 ) }>
Previous
</Button>
) }
{ posts.hasMore && (
<Button onClick={ () => onNextPage( 1 ) }>
Next
</Button>
) }
</div>
) ); |
Thanks, @aduth really helpful. Do you think we should add support of multiple pages (multiple requests with the same HoC) with the same |
You could currently do something like: withAPIData( {
page1: `/wp/v2/posts?page=1`,
page2: `/wp/v2/posts?page=2`,
} )( MyList ); But I think in general cases it might be better to have a parent component which manages those individual pages, as in the above example. Let's revisit if we encounter it being an issue in practice. |
// See: gutenberg_ensure_wp_api_request (compat.php). | ||
gutenberg_register_vendor_script( | ||
'wp-api-request-shim', | ||
'https://rawgit.com/WordPress/wordpress-develop/master/src/wp-includes/js/api-request.js' |
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.
@aduth, even though this is a temporary measure, shouldn't we prefer a cdn.rawgit.com
-type URL?
Per their homepage:
[cdn.rawgit.com] No traffic limits or throttling. Files are served via StackPath's super fast global CDN.
vs.
[rawgit.com] Excessive traffic will be throttled and blacklisted.
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.
Now that you mention it, I'd meant to look if we need to use rawgit.com at all, or if we can just reference the GitHub.com link directly. The latter is blocked for script tags in the browser, but since this occurs server-side, we might be able to reference the raw JS file straight from the source.
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.
Also worth noting that we're not sending much traffic here. The plugin includes a pre-downloaded copy, and in development the script is downloaded and cached for 24 hours.
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.
Fair enough. :)
Related: #902
This pull request seeks to introduce a new
withAPIData
higher-order component to provide a simple interface for fetching, creating, updating, or deleting data from the REST API.Out of the box, it includes:
Example:
Implementation notes:
withApiData
behaves similar to, and in some cases complements, React-Redux'sconnect
function. It accepts props which can either be passed from the parent component, or composed from another higher-order component (e.g.connect
), and returns an object of propName -> endpoint result.Data-bound props take the shape of an object with a number of properties, depending on the methods supported for the particular endpoint:
GET
isLoading
: Whether the resource is currently being fetcheddata
: The resource, available once fetch succeedsget
: Function to invoke a new fetch requesterror
: The error object, if the fetch failedPOST
isCreating
: Whether the resource is currently being createdcreatedData
: The created resource, available once create succeedscreate
: Function to invoke a new create requestcreateError
: The error object, if the create failedPUT
isSaving
: Whether the resource is currently being savedsavedData
: The saved resource, available once save succeedssave
: Function to invoke a new save requestsaveError
: The error object, if the save failedPATCH
isPatching
: Whether the resource is currently being patchedpatchedData
: The patched resource, available once patch succeedspatch
: Function to invoke a new patch requestpatchError
: The error object, if the patch failedDELETE
isDeleting
: Whether the resource is currently being deleteddeletedData
: The deleted resource, available once delete succeedsdelete
: Function to invoke a new delete requestdeleteError
: The error object, if the delete failedThere is some inspiration here from Heroku's
react-refetch
project.Testing instructions:
Verify that there are no regressions in the LastRevision component (requires post with at least one revision to appear).
See original pull request comment: https://gist.github.com/aduth/e5dd6327963475e9f580b8ece523ffa0