Skip to content

Commit deb7076

Browse files
committed
refactor: optimize LWC components with performance improvements
Performance improvements: - Added memoization for expensive computations in bikeFilters, bikePagination, and cartStore - Cached currency formatter in cartSummary to avoid repeated Intl.NumberFormat instantiation - Implemented state caching with dirty flag tracking in cartStore for efficient updates Code optimization: - Extracted constants across all components for better maintainability - Simplified event dispatching patterns and removed redundant composed: false - Enhanced accessibility with proper ARIA attributes and navigation labels - Consolidated patterns using arrow functions and modern ES2022 features - Improved ID generation using crypto.randomUUID with fallback strategy
1 parent 6d609b9 commit deb7076

File tree

8 files changed

+446
-281
lines changed

8 files changed

+446
-281
lines changed

src/modules/my/bikeCard/bikeCard.js

Lines changed: 42 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,78 +1,80 @@
11
import { LightningElement, api } from 'lwc';
22

3+
// Constants
4+
const DEFAULT_IMAGE_URL = 'https://placehold.co/300x200';
5+
const KEYBOARD_KEYS = {
6+
ENTER: 'Enter',
7+
SPACE: ' ',
8+
};
9+
310
export default class BikeCard extends LightningElement {
411
@api bike;
5-
612
selectedColor = '';
713

814
connectedCallback() {
9-
// Set default color
10-
if (this.bike?.colors?.length > 0) {
11-
this.selectedColor = this.bike.colors[0];
12-
}
15+
// Initialize selected color when bike data is available
16+
this.selectedColor = this.bike?.colors?.[0] || '';
1317
}
1418

1519
handleColorChange(event) {
1620
this.selectedColor = event.target.value;
1721
}
1822

1923
handleAddToCart() {
20-
// Event naming per lwc.dev: no uppercase, underscores OK
21-
const addEvent = new CustomEvent('add_to_cart', {
22-
bubbles: true,
23-
composed: false,
24-
detail: {
25-
// Send only primitives (lwc.dev best practice)
26-
bikeId: this.bike.id,
27-
selectedColor: this.selectedColor,
28-
quantity: 1,
29-
},
30-
});
31-
32-
this.dispatchEvent(addEvent);
24+
this.dispatchEvent(
25+
new CustomEvent('add_to_cart', {
26+
bubbles: true,
27+
detail: {
28+
bikeId: this.bike.id,
29+
selectedColor: this.selectedColor,
30+
quantity: 1,
31+
},
32+
})
33+
);
3334
}
3435

3536
handleViewDetails() {
36-
const detailEvent = new CustomEvent('view_product_detail', {
37-
bubbles: true,
38-
composed: false,
39-
detail: {
40-
bikeId: this.bike.id,
41-
},
42-
});
43-
44-
this.dispatchEvent(detailEvent);
37+
this.dispatchEvent(
38+
new CustomEvent('view_product_detail', {
39+
bubbles: true,
40+
detail: {
41+
bikeId: this.bike.id,
42+
},
43+
})
44+
);
4545
}
4646

4747
handleKeyDown(event) {
4848
// Handle Enter and Space key for accessibility
49-
if (event.key === 'Enter' || event.key === ' ') {
49+
if (event.key === KEYBOARD_KEYS.ENTER || event.key === KEYBOARD_KEYS.SPACE) {
5050
event.preventDefault();
5151
this.handleViewDetails();
5252
}
5353
}
5454

5555
get currentImage() {
5656
// Use color-specific image if available, fallback to default image
57-
if (this.bike?.images && this.selectedColor && this.bike.images[this.selectedColor]) {
58-
const colorImages = this.bike.images[this.selectedColor];
59-
// Use first image from array if it's an array, otherwise use as string
57+
const colorImages = this.bike?.images?.[this.selectedColor];
58+
if (colorImages) {
6059
return Array.isArray(colorImages) ? colorImages[0] : colorImages;
6160
}
62-
return this.bike?.image || 'https://placehold.co/300x200';
61+
return this.bike?.image || DEFAULT_IMAGE_URL;
6362
}
6463

65-
get formattedPrice() {
64+
// Utility method for formatting currency
65+
formatCurrency(amount) {
6666
return new Intl.NumberFormat('en-US', {
6767
style: 'currency',
6868
currency: 'USD',
69-
}).format(this.bike?.price || 0);
69+
}).format(amount || 0);
7070
}
7171

72-
get colorOptions() {
73-
if (!this.bike?.colors) return [];
72+
get formattedPrice() {
73+
return this.formatCurrency(this.bike?.price);
74+
}
7475

75-
return this.bike.colors.map(color => ({
76+
get colorOptions() {
77+
return (this.bike?.colors || []).map(color => ({
7678
label: color.charAt(0).toUpperCase() + color.slice(1),
7779
value: color,
7880
selected: this.selectedColor === color,
@@ -84,9 +86,9 @@ export default class BikeCard extends LightningElement {
8486
}
8587

8688
get stockBadgeClass() {
87-
return this.bike?.inStock
88-
? 'slds-badge slds-badge_lightest slds-theme_success'
89-
: 'slds-badge slds-badge_lightest slds-theme_error';
89+
const baseClasses = 'slds-badge slds-badge_lightest';
90+
const themeClass = this.bike?.inStock ? 'slds-theme_success' : 'slds-theme_error';
91+
return `${baseClasses} ${themeClass}`;
9092
}
9193

9294
get colorSelectId() {

src/modules/my/bikeDetail/bikeDetail.js

Lines changed: 49 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,26 @@
11
import { LightningElement, api } from 'lwc';
22

3+
// Constants
4+
const DEFAULT_IMAGE_URL = 'https://placehold.co/600x400';
5+
const KEYBOARD_KEYS = {
6+
ESCAPE: 'Escape',
7+
ARROW_LEFT: 'ArrowLeft',
8+
ARROW_RIGHT: 'ArrowRight',
9+
ENTER: 'Enter',
10+
SPACE: ' ',
11+
};
12+
313
export default class BikeDetail extends LightningElement {
414
@api bike;
515
selectedColor = '';
616
quantity = 1;
717
isImageZoomed = false;
818
currentImageIndex = 0;
19+
boundKeyHandler;
920

1021
connectedCallback() {
11-
if (this.bike?.colors?.length > 0) {
12-
this.selectedColor = this.bike.colors[0];
13-
}
22+
// Initialize selected color
23+
this.selectedColor = this.bike?.colors?.[0] || '';
1424

1525
// Bind keyboard handler for zoom navigation
1626
this.boundKeyHandler = this.handleZoomKeyDown.bind(this);
@@ -34,34 +44,32 @@ export default class BikeDetail extends LightningElement {
3444
}
3545

3646
handleAddToCart() {
37-
const addEvent = new CustomEvent('add_to_cart', {
38-
bubbles: true,
39-
composed: false,
40-
detail: {
41-
bikeId: this.bike.id,
42-
selectedColor: this.selectedColor,
43-
quantity: this.quantity,
44-
},
45-
});
46-
47-
this.dispatchEvent(addEvent);
47+
this.dispatchEvent(
48+
new CustomEvent('add_to_cart', {
49+
bubbles: true,
50+
detail: {
51+
bikeId: this.bike.id,
52+
selectedColor: this.selectedColor,
53+
quantity: this.quantity,
54+
},
55+
})
56+
);
4857
}
4958

5059
handleBackToStore() {
51-
const backEvent = new CustomEvent('back_to_store', {
52-
bubbles: true,
53-
composed: false,
54-
});
55-
56-
this.dispatchEvent(backEvent);
60+
this.dispatchEvent(
61+
new CustomEvent('back_to_store', {
62+
bubbles: true,
63+
})
64+
);
5765
}
5866

5967
handleImageClick() {
6068
this.isImageZoomed = !this.isImageZoomed;
6169
}
6270

6371
handleImageKeyDown(event) {
64-
if (event.key === 'Enter' || event.key === ' ') {
72+
if (event.key === KEYBOARD_KEYS.ENTER || event.key === KEYBOARD_KEYS.SPACE) {
6573
event.preventDefault();
6674
this.handleImageClick();
6775
}
@@ -71,25 +79,20 @@ export default class BikeDetail extends LightningElement {
7179
this.isImageZoomed = false;
7280
}
7381

74-
handleZoomPrevious() {
75-
this.handlePreviousImage();
76-
}
77-
78-
handleZoomNext() {
79-
this.handleNextImage();
80-
}
82+
handleZoomPrevious = () => this.handlePreviousImage();
83+
handleZoomNext = () => this.handleNextImage();
8184

8285
handleZoomKeyDown(event) {
8386
if (this.isImageZoomed) {
8487
switch (event.key) {
85-
case 'Escape':
88+
case KEYBOARD_KEYS.ESCAPE:
8689
this.handleZoomClose();
8790
break;
88-
case 'ArrowLeft':
91+
case KEYBOARD_KEYS.ARROW_LEFT:
8992
event.preventDefault();
9093
this.handlePreviousImage();
9194
break;
92-
case 'ArrowRight':
95+
case KEYBOARD_KEYS.ARROW_RIGHT:
9396
event.preventDefault();
9497
this.handleNextImage();
9598
break;
@@ -125,40 +128,36 @@ export default class BikeDetail extends LightningElement {
125128

126129
get currentImages() {
127130
// Get all images for the selected color
128-
if (this.bike?.images && this.selectedColor && this.bike.images[this.selectedColor]) {
129-
const colorImages = this.bike.images[this.selectedColor];
131+
const colorImages = this.bike?.images?.[this.selectedColor];
132+
if (colorImages) {
130133
return Array.isArray(colorImages) ? colorImages : [colorImages];
131134
}
132-
return [this.bike?.image || 'https://placehold.co/600x400'];
135+
return [this.bike?.image || DEFAULT_IMAGE_URL];
133136
}
134137

135138
get currentImage() {
136139
// Get the currently selected image from the gallery
137140
const images = this.currentImages;
138-
return (
139-
images[this.currentImageIndex] ||
140-
images[0] ||
141-
this.bike?.image ||
142-
'https://placehold.co/600x400'
143-
);
141+
return images[this.currentImageIndex] || images[0] || DEFAULT_IMAGE_URL;
144142
}
145143

146-
get formattedPrice() {
144+
// Utility method for formatting currency
145+
formatCurrency(amount) {
147146
return new Intl.NumberFormat('en-US', {
148147
style: 'currency',
149148
currency: 'USD',
150-
}).format(this.bike?.price || 0);
149+
}).format(amount || 0);
150+
}
151+
152+
get formattedPrice() {
153+
return this.formatCurrency(this.bike?.price);
151154
}
152155

153156
get formattedOriginalPrice() {
154157
if (!this.bike?.originalPrice || this.bike.originalPrice === this.bike.price) {
155158
return null;
156159
}
157-
158-
return new Intl.NumberFormat('en-US', {
159-
style: 'currency',
160-
currency: 'USD',
161-
}).format(this.bike.originalPrice);
160+
return this.formatCurrency(this.bike.originalPrice);
162161
}
163162

164163
get hasDiscount() {
@@ -192,16 +191,13 @@ export default class BikeDetail extends LightningElement {
192191
}
193192

194193
get frameSize() {
195-
if (!this.bike?.frameSize) return 'Various sizes available';
196-
return this.bike.frameSize.join(', ');
194+
return this.bike?.frameSize?.join(', ') || 'Various sizes available';
197195
}
198196

199197
get specifications() {
200-
if (!this.bike?.specs) return [];
201-
202-
return Object.entries(this.bike.specs).map(([key, value]) => ({
198+
return Object.entries(this.bike?.specs || {}).map(([key, value]) => ({
203199
label: key.charAt(0).toUpperCase() + key.slice(1),
204-
value: value,
200+
value,
205201
}));
206202
}
207203

0 commit comments

Comments
 (0)