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
Style selector basic functionality #76
Changes from 20 commits
94e2520
af29dab
dcf76cd
e2745b4
c28600f
f144564
0aa29b1
960b89a
aee3638
5cecaa7
9b619e0
02d093a
b3d1270
cd023bd
c80ab4b
77f5535
91b00c5
e509db9
3cda741
e7568fa
d1892bd
89db6d8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,10 @@ | ||
import React from 'react'; | ||
import PropTypes from 'prop-types'; | ||
import StyleSelector from 'Overview/StyleSelector.jsx'; | ||
import AddToCart from 'Overview/AddToCart.jsx'; | ||
|
||
/** | ||
* Shows general product information | ||
*/ | ||
function ProductInformation({ product, styles, selectedStyleId, handleStyleSelect }) { | ||
// TODO: how to avoid optional chaining? | ||
const selectedStyle = styles?.find((style) => style.style_id === selectedStyleId); | ||
|
||
function ProductInformation({ product, selectedStyle}) { | ||
return ( | ||
<div> | ||
{/*TODO: add rating stars*/} | ||
|
@@ -22,25 +17,17 @@ function ProductInformation({ product, styles, selectedStyleId, handleStyleSelec | |
Style > {selectedStyle?.name} | ||
<br /> | ||
{/*TODO: add strikethrough for sale*/} | ||
{selectedStyle?.original_price} | ||
{selectedStyle?.sale_price !== 0 ? selectedStyle?.sale_price : null} | ||
{/*TODO: show Product Overview*/} | ||
{/*TODO: add Share buttons*/} | ||
<StyleSelector | ||
styles={styles} | ||
selectedStyleId={selectedStyleId} | ||
handleStyleSelect={handleStyleSelect} | ||
/> | ||
<AddToCart /> | ||
{selectedStyle?.original_price} | ||
{selectedStyle?.sale_price !== 0 ? selectedStyle?.sale_price : null} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Seems this is specific to one product, so maybe |
||
</div> | ||
); | ||
} | ||
|
||
ProductInformation.propTypes = { | ||
product: PropTypes.object.isRequired, | ||
styles: PropTypes.array.isRequired, | ||
selectedStyleId: PropTypes.number.isRequired, | ||
handleStyleSelect: PropTypes.func.isRequired, | ||
selectedStyle: PropTypes.object.isRequired, | ||
}; | ||
|
||
export default ProductInformation; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,51 @@ | ||
import React from 'react'; | ||
import PropTypes from 'prop-types'; | ||
|
||
function StyleSelector() { | ||
return null; | ||
/** | ||
* Displays thumbnails of the product styles and | ||
* allows style selection by clicking on a thumbnail | ||
*/ | ||
function Style({ style, handleStyleSelect, selected }) { | ||
// TODO: handle selected overlay | ||
return ( | ||
<div onClick={() => handleStyleSelect(style.style_id)}> | ||
<img | ||
src={style.photos[0].thumbnail_url} | ||
alt={`${style.name} style thumbnail`} | ||
width="50" | ||
/> | ||
</div> | ||
); | ||
} | ||
|
||
Style.propTypes = { | ||
style: PropTypes.object.isRequired, | ||
handleStyleSelect: PropTypes.func.isRequired, | ||
selected: PropTypes.bool.isRequired, | ||
}; | ||
|
||
function StyleSelector({ styles, selectedStyleId, handleStyleSelect }) { | ||
// TODO: use color-thief to extract average color of thumbnails server-side | ||
const stylesArray = styles.map((style) => ( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can just directly return this and skip setting to a var then calling it later There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes good point. I think I had separated it in case I had more complicated logic in the return statement, but that ended up not being the case. |
||
<Style | ||
key={style.style_id} | ||
style={style} | ||
handleStyleSelect={handleStyleSelect} | ||
selected={style.styled_id === selectedStyleId} | ||
/> | ||
)); | ||
|
||
return ( | ||
<div> | ||
{ stylesArray } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Put this directly in here for easier readability. But seems this component is maybe not even needed as it's essentially a map function over the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, StyleSelector is only used once. I did it like this to try to compartmentalize things and keep There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually, I realize now that's not even what I'm doing. Taking your other comment into account, I'll move |
||
</div> | ||
); | ||
} | ||
|
||
StyleSelector.propTypes = { | ||
styles: PropTypes.array.isRequired, | ||
selectedStyleId: PropTypes.number.isRequired, | ||
handleStyleSelect: PropTypes.func.isRequired, | ||
}; | ||
|
||
export default StyleSelector; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import { screen } from '@testing-library/react'; | ||
import StyleSelector from 'Overview/StyleSelector.jsx'; | ||
|
||
describe('StyleSelector', () => { | ||
it('should change selected style when a thumbnail is clicked', async () => { | ||
const user = userEvent.setup(); | ||
const handleStyleSelect = (styleId) => { | ||
selectedStyleId = styleId; | ||
}; | ||
let selectedStyleId = 1; | ||
|
||
const {rerender} = render(<StyleSelector | ||
styles={testData.styles.results} | ||
selectedStyleId={selectedStyleId} | ||
handleStyleSelect={handleStyleSelect} | ||
/>); | ||
|
||
//TODO: might need to be more specific as thumbnails get added to imageGallery | ||
const style = 'Desert Brown & Tan'; | ||
await user.click(screen.getByAltText(new RegExp(style))); | ||
|
||
const selectedStyleObj = Object.values(testData.styles.results) | ||
.find(styleObj => styleObj.style_id === selectedStyleId); | ||
const selectedStyle = selectedStyleObj.name; | ||
|
||
expect(selectedStyle).toBe(style); | ||
}); | ||
|
||
it.todo('should show all styles for a product'); | ||
it.todo('should initially select the default style <- should go in Overview') | ||
it.todo('should not change style when the currently selected style thumbnail is clicked'); | ||
it.todo('should indicate the selected style with an overlay'); | ||
}); |
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 can initialize state lazily: https://reactjs.org/docs/hooks-reference.html#lazy-initial-state
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.
Does this provide an advantage over
useEffect
without subscribers? I also need to setproduct
andstyles
from data that I pull from an API. But the data is fetched only on the initial render since the subscribers array is empty. I'm now realizing that the useEffect hook should run whenproductId
changes though, since the data should be refetched whenever a new product is being displayed.