-
Notifications
You must be signed in to change notification settings - Fork 144
Conversation
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.
In all, I think this is a fairly good implementation, but I wonder what it's getting us that withApiData
didn't. I guess it pulls the API paths out of the components, which is good, at least.
I wonder how this approach will scale over time, and I also wonder how much of this code we'd have to change if we tried to run it within Calypso or some other environment.
} | ||
|
||
export default compose( | ||
withSelect( select => { |
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 like how clean this function is.
}; | ||
} ), | ||
withDispatch( dispatch => { | ||
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.
I'm not sure how I feel about having component logic here. If we do this a lot, the compose function will get pretty crowded.
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.
Agree here. This is where the need to write our own middleware comes into play so that we can write thunks. Ideally it would look like this:
withDispatch( dispatch => {
return {
onToggleStatus: dispatch( 'woo-dash' ).toggleOrderStatus,
};
} )
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 is this where you see the resolvers at work?
this is where I see requestHttpData()
coming into play since it can trigger side-effects and through Redux update state on those transitions without breaking from the synchronous paradigm or resorting to thunks
return { | ||
onToggleStatus: function( order ) { | ||
const status = order.status === 'completed' ? 'processing' : 'completed'; | ||
order.status = status; |
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.
Should we be mutating what is passed in here? Could this cause unforeseen problems?
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.
no, and then yes 😉
}; | ||
case 'UPDATE_ORDER': | ||
const orderIndex = state.orders.findIndex( order => order.id === action.order.id ); | ||
const orders = state.orders.map( o => o ); |
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.
Looks like this isn't actually mapping anything. Maybe const orders = { ...state.orders }
or const orders = Object.assign( {}, state.orders );
instead?
orders: action.orders, | ||
}; | ||
case 'UPDATE_ORDER': | ||
const orderIndex = state.orders.findIndex( order => order.id === action.order.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.
Array.prototype.findIndex
isn't supported by IE, so this may need a polyfill if we want any IE11 support. Or use a lodash equivalent instead.
Also, I wonder if storing orders by id might be more optimal, then the findIndex
wouldn't be necessary at all.
return state; | ||
}, | ||
|
||
actions: { |
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.
Note on @wordpress/data
: I'm not a huge fan of this. In order to implement all of the WooCommerce API, we'll need a lot of these actions and they will all be very repetitive.
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.
@coderkevin do you think there would be a reasonably way to generate these structures given a helper function? for example, with the structural similarity between endpoints and freshData
could you have a sort of builder function given a type or kind of API data?
resolvers: { | ||
async getOrders() { | ||
try { | ||
const orders = await apiRequest( { path: '/wc/v2/orders' } ); |
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.
Implementation note: While the path is simple here, after it's been copy/pasted to all endpoints, it may be annoying to update API version numbers in the future.
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 is this a good place for the apiRequest
middleware? send /orders
or some version-less path then have the middleware add the path?
with Calypso's data layer we'd simply intercept the WPCOM_HTTP_REQUEST
or RAW_HTTP_REQUEST
actions with custom Redux middleware and change the path property before passing along the 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.
@aduth is this a good place for the apiRequest middleware? send /orders or some version-less path then have the middleware add the path?
Since apiRequest
is a singleton working in a global space, adding a middleware which prefixes the WooCommerce-specific path prefixes could be problematic.
Maybe apiRequest
here could point to some internal module which just proxies to the underlying @wordpress/api-request
with the prefix attached?
const orders = await apiRequest( { path: '/wc/v2/orders' } ); | ||
dispatch( 'woo-dash' ).setOrders( orders ); | ||
} catch ( error ) { | ||
alert( error.responseJSON.message ); |
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.
Obviously temporary code here, but we should be careful to not assume every error has a responseJSON
*/ | ||
import apiRequest from '@wordpress/api-request'; | ||
|
||
export async function requestUpdateOrder( dispatch, order ) { |
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 like how fairly simple this is. I kind of wish it were in the same place as the rest of the orders stuff though.
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.
Yup, should be an action, along with others
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.
One quick comment from me, testing out nicely. Thanks for exploring this route Paul
resolvers: { | ||
async getOrders() { | ||
try { | ||
const orders = await apiRequest( { path: '/wc/v2/orders' } ); |
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.
any reason to not use /wc/v3
here?
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 are the requirements for v3? I'm running WooCommerce version 3.4.3
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.
@psealock There is a feature branch but it is behind master now.
Installing wc-api-dev should get you the endpoints in a separate plugin. Though if development is happening in the feature branch -- we should make sure to either keep that up to date, or have v3 in master. cc @claudiulodro
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've added a mention of this to the README for now https://github.com/woocommerce/wc-admin#prerequisites
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.
The one in the WC master branch is going to have to be the development version while we finish up the v3 API, as we're doing some refactoring in the old API versions as part of it.
I would use wc-api-dev until we get the first stats endpoints done instead of the WC branch, as wc-api-dev is currently stable and the branch is currently in flux with many changes happening and some known bugs.
import apiRequest from '@wordpress/api-request'; | ||
|
||
export async function requestUpdateOrder( dispatch, order ) { | ||
// Lets be optimistic |
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.
🌠
Thanks for the reviews @coderkevin @timmyc I adjusted according to feedback. The most important update is some additional middleware so that thunks can be used to clean up how actions are used. Now, you can create an action creator when registering your store const store = registerStore( 'woo-dash', {
reducer(){ ... },
actions: {
doAsync() {
return requestUpdateOrder () => {
dispatch( 'woo-dash' ).updateOrder( order );
try {
const updatedOrder = await apiRequest( {
path: '/wc/v2/orders/' + order.id,
method: 'PUT',
data: order,
} );
dispatch( 'woo-dash' ).updateOrder( updatedOrder );
} catch ( error ) {
alert( error.responseJSON.message );
}
};
}
},
selectors: {},
resolvers: {},
} And access it like you would any other action withDispatch( dispatch => {
return {
requestUpdateOrder: function( updatedOrder ) {
dispatch( 'woo-dash' ).requestUpdateOrder( updatedOrder );
},
};
} ) I quickly wrote some middleware logic without test or anything. I also ignored a couple comments for simplicity for readability, but will definitely address these things should we move forward with this branch. I think its is ready for a side by side comparison of |
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 like the way this is going, but I think we should set up some standards - naming and file/folder structure - and stick to them here before we go too far in any direction. I'm thinking things like state structure, action/selector naming patterns, and separating out reducers/actions into object-type subfolders, etc
(I also didn't review the code in OrdersReport, since i assume it's just placeholder)
orderIds: [], | ||
}; | ||
|
||
const store = registerStore( 'woo-dash', { |
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 happens when we start adding other object-types to this? Presumably we'll be tracking products too, customers, reviews, categories, etc. Can we still use things like combineReducers, to keep reducers organized?
And (eventually) for extensions, would they just register a different store?
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.
Can we still use things like combineReducers, to keep reducers organized?
Yes! I will add some structure in today to address this and the points made in your parent comment
And (eventually) for extensions, would they just register a different store?
Yup, thats right. In fact, while registering their own store, Gutenberg's dispatch
will give them access to selectors we create here via dispatch( 'woo-dash' )...
return state; | ||
}, | ||
|
||
actions: { |
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 have the same question about actions + more object types, but it's easier to see this as a list of imported functions at least.
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.
It looks like actions
and selectors
will need to be simple flat objects with uniquely named functions.
@ryelle I've made a first pass at organizing folders and structures, including using |
Thanks for taking this on @psealock! |
Great to see the data module being explored. A small not on the "thunk" and action creators in general. We're not satisfied with the way we create/use these middlewares in the data module and we're looking at implementing built-in support for async actions so users of the data module don't have to care about these middlewares and just write their actions (similarily to the resolvers). |
Thanks for chiming in @youknowriad - I'm really curious about discussing the paradigm in Calypso's data layer for this stuff. I'm biased since I designed it but I feel like it's a good fit for all this work without the complexity of thunks/generators/implicit properties. I guess it mainly boils down to "everything is data" but using the action-watchers middleware alongside the http middleware, where action watchers dispatch new HTTP actions in order to fulfill data requests and updates.
the only thing I have yet to include that I think is important is giving components the ability to temporarily inject their own action watches: maybe this UI panel wants to toss around a floating star every time a post is liked, for example. we should be able to do so by watching the hope this doesn't sound like rambling. |
@psealock given the decision to move forward with this approach - do you mind giving this branch a rebase - and are there any other changes you would like to make before we merge this in? |
dd5eba6
to
3b32ac7
Compare
This one is rebased against master. Its an overall fairly simplistic example, but a good starting off point for exploring how to make Any objections, fixes, comments before merging? |
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.
Few comments, but I agree this is a great launching point for us with wp.data
and would love to get it merged in! When trying to test just now, I'm getting an empty screen and the following error:
index.js?ver=1532447154:8835 Uncaught Error: Cannot find module 'redux'
at webpackMissingModule (index.js?ver=1532447154:8835)
at Module../client/store/index.js (index.js?ver=1532447154:8835)
at __webpack_require__ (bootstrap:19)
at Module../client/index.js (index.js?ver=1532447154:5690)
at __webpack_require__ (bootstrap:19)
at bootstrap:83
at bootstrap:83
I tried blowing away my node_modules
directory.
|
||
dispatch( 'wc-admin' ).updateOrder( updatedOrder ); | ||
} catch ( error ) { | ||
if ( error && error.responseJSON ) { |
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 this is fine for now, but we will want to come up with a standard way to handle these sorts of exceptions.
export default { | ||
async getOrders() { | ||
try { | ||
const orders = await apiRequest( { path: '/wc/v3/orders' } ); |
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.
This will also need to handle query args correct?
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.
Right, it will need to handle those as well. @coderkevin is looking into incorporating some features of fresh-data into wp.data, hopefully including using query args as keys for retrieving already fetched data. I think its a good idea to revisit this as his efforts are further along
3b32ac7
to
adec335
Compare
@timmyc updated to Gutenberg 3.3. It should work now |
@@ -0,0 +1,42 @@ | |||
/** @format */ |
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.
If this is to be merged in for reals, store
should be under client
instead.
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.
@psealock seems like a valid point, should we move this over?
d07c4f0
to
bbac546
Compare
|
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.
Thanks for the updates @psealock and all your explorations here 🙌
Update to Gutenberg 3.3
bbac546
to
c361948
Compare
This is an example of how we might use wp.data for handling wc-admin data needs.
Test
/wp-admin/admin.php?page=woodash#/analytics/orders
completed
andprocessing
The good
withRehydration
functions for storing and reading from localStorageThe not so good