-
Notifications
You must be signed in to change notification settings - Fork 219
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.
Hey @Aljullu this is a great first attempt!
- there's some js unit test errors that will need looked into.
- There's still some coupling here. Imo
withGetProduct
should simply bewithProduct
(or maybewithDebouncedProduct
to describe that it is debounced and essentially contains ALL the logic for the retrieval of the product. Then essentially the existingFeaturedProduct
block could be a functional component because all it would contain is the contents of render (getInspectorControls
could be a function internal to the component or external receiving arguments (internal probably would be fine).
43fa28c
to
c1e6e32
Compare
@nerrad thanks for taking a look!
Right, they were fixed in #781. I rebased and they should be passing again.
Agree, my initial thought was to keep I did that and converted
I'm curious about this, I probably would have done them external. Is there any pros and cons of each approach? |
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.
👍 looking good. I still have a few comments for feedback. As well, I noticed you've removed the debouncedGetProduct
logic. Was that intentional? I was expecting to see it in the new withProduct
hoc, or is that something that you felt was no longer necessary?
Also, now that data logic has been separated from presentation, what are your thoughts on adding tests for the withProduct
hoc?
I'm curious about this, I probably would have done them external. Is there any pros and cons of each approach?
Pros:
- within scope of the component so you don't have to pass the arguments around
- it's clear the functions are specific to the component.
Cons:
- be wary of passing along the function as a callback on a child element as a prop. The function is re-created every time the main (parent) component is rendered so that can cause unnecessary re-renders for children. So in that case you definitely DO want to have it as an external function (or implemented with
useCallback
oruseMemo
- depending on the use-case if using React hooks).
Another consideration on whether it should be internal or external I think would be on whether there's potential for exposing the functions (especially if they are more like functional components themselves! for other consuming code).
import { | ||
getImageSrcFromProduct, | ||
getImageIdFromProduct, | ||
} from '../../utils/products'; | ||
import withProduct from '../../utils/with-product'; |
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 wonder if we should put hocs in a top-level (living at the same level as components
) folder? In the pull I'm working on I have js/hocs/with-searched-products.js
. I don't think we should initially expose hocs publicly yet, but it does provide the option down the road for exposing them on their own package (and makes it easier to locate them too).
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.
Absolutely. Done in 72abc25.
In a follow-up, we will need to move with-component-id.js
too.
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 a follow-up, we will need to move with-component-id.js too.
Done in #797.
|
||
renderEditMode() { | ||
const { attributes, debouncedSpeak, setAttributes } = this.props; | ||
const FeaturedProduct = ( { attributes, debouncedSpeak, error, getProduct, isLoading, isSelected, overlayColor, product, setAttributes, setOverlayColor } ) => { |
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.
Regarding getProduct
I don't think the presentation component should receive any getter but instead only the values it uses for rendering. I realize that it's being used as the onRetry
method for ApiErrorPlaceholder
but this to me smells like ApiErrorPlaceholder
should be wrapped with the withGetProduct
Hoc and exposed as ProductApiErrorPlaceholder
which can then be used here. That would remove the data coupling here.
Alternatively, in future refactors we could look at having error placeholders passed in as render props which would give more flexibility to code consuming the presentation components for how errors are handled.
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 been investigating around this idea but I'm not sure to understand it yet. What would be the benefit of wrapping ApiErrorPlaceholder
with withProduct
if the parent component is already wrapped by that HOC? That means we are re-rendering the error component at least once just to get the props
which were already loaded in its parent.
Am I missing something? I created a WIP here:
… error when 'product' is not an object
72abc25
to
cc43716
Compare
Thanks for the review again @nerrad.
So I didn't see any use-case for debouncing the function.
Done in cc43716. 👍 |
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 changes. Awesome!
Regarding the ProductApiPlaceholder. I think it's a bit more clearer to me how it would work now that you extracted getProduct( id: number )
in 4aebc34.
So what I envision now is that you'd have something like:
import { ApiErrorPlaceHolder } from './components';
import { getProduct } from './utils';
export const ProductApiErrorPlaceHolder = ( {
className,
error,
isLoading,
productId,
} ) => {
return error ?
<ApiErrorPlaceHolder
className={ className }
error={ error }
isLoading={ isLoading }
onRetry={ () => getProduct( productId ) }
/> :
null;
};
That way you don't have to pass the getProduct
(essentially the new loadProduct
in the hoc) through to the wrapped component. That makes the HOC a little bit more guarded and you're not exposing the data retrieval logic (which makes it more difficult to eventually export that publicly).
I think it's important to keep any data retrieval encapsulated as much as possible in the components responsible for it. This way you can also keep ApiErrorPlaceholder
as an internal only component but we could eventually expose the specific ApiErrorPlaceholders
for other components publicly for use (such as ProductApiErrorPlaceholder
because for example in this case, we can change the internals for how a product is retrieved onRetry, but consuming code doesn't have to care about it).
Ugh 🤦♂ ... I just realized the severe flaw with the comments I made about the |
I implemented your suggestions @nerrad, this PR is ready for another review. |
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 great!
Note, I haven't tested the existing functionality of the block as per the pull notes, but code wise this looks good to go.
This PR includes several changes:
getProduct()
to a HOC following @nerrad's suggestion for the<ProductsControl>
component. This way, we will be able to reusegetProduct()
in Reviews by Product without duplicating the logic.<FeaturedProduct>
to a functional component.retrying
state fromApiErrorPlaceholder
and make it rely on aisLoading
prop, this way we don't have to maintain the same state in two different components.ApiErrorPlaceholder
.How to test the changes in this Pull Request:
To test, basically add a Featured Product block and verify it still works as expected.
[Bonus points] Test that errors are still displayed: