Skip to content

Commit d4d51d1

Browse files
committed
Refactor
1 parent 1858b6a commit d4d51d1

File tree

5 files changed

+129
-104
lines changed

5 files changed

+129
-104
lines changed

src/components/Cart/CartContents.component.tsx

Lines changed: 44 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,7 @@ import Button from '@/components/UI/Button.component';
1010

1111
import {
1212
getFormattedCart,
13-
getUpdatedItems,
1413
handleQuantityChange,
15-
IProductRootObject,
1614
} from '@/utils/functions/functions';
1715

1816
import { GET_CART } from '@/utils/gql/GQL_QUERIES';
@@ -23,146 +21,113 @@ const CartContents = () => {
2321
const { cart, setCart } = useCartStore();
2422
const isCheckoutPage = router.pathname === '/kasse';
2523

26-
const { data } = useQuery(GET_CART, {
24+
useQuery(GET_CART, {
2725
notifyOnNetworkStatusChange: true,
2826
onCompleted: (data) => {
27+
// Only update if there's a significant difference to avoid unnecessary re-renders
2928
const updatedCart = getFormattedCart(data) as RootObject | undefined;
30-
setCart(updatedCart || null);
29+
if (!cart || cart.totalProductsCount !== updatedCart?.totalProductsCount) {
30+
setCart(updatedCart || null);
31+
}
3132
},
3233
});
3334

34-
const [updateCart] = useMutation(UPDATE_CART, {
35-
refetchQueries: [{ query: GET_CART }],
36-
awaitRefetchQueries: true,
37-
});
38-
39-
const handleRemoveProductClick = (
40-
cartKey: string,
41-
products: IProductRootObject[],
42-
) => {
43-
if (products?.length) {
44-
// Optimistically update local state
45-
const currentCart = cart;
46-
if (currentCart) {
47-
const updatedProducts = currentCart.products.filter((p: Product) => p.cartKey !== cartKey);
48-
setCart({
49-
...currentCart,
50-
products: updatedProducts,
51-
totalProductsCount: currentCart.totalProductsCount - 1
52-
});
53-
}
35+
const [updateCart] = useMutation(UPDATE_CART);
5436

55-
// Update remote state in background
56-
const updatedItems = getUpdatedItems(products, 0, cartKey);
57-
updateCart({
58-
variables: {
59-
input: {
60-
clientMutationId: uuidv4(),
61-
items: updatedItems,
62-
},
63-
},
37+
const handleRemoveProductClick = (cartKey: string) => {
38+
// Optimistically update local state
39+
if (cart) {
40+
const updatedProducts = cart.products.filter((p: Product) => p.cartKey !== cartKey);
41+
const removedProduct = cart.products.find((p: Product) => p.cartKey === cartKey);
42+
const qtyRemoved = removedProduct?.qty || 0;
43+
44+
setCart({
45+
...cart,
46+
products: updatedProducts,
47+
totalProductsCount: cart.totalProductsCount - qtyRemoved
6448
});
6549
}
66-
};
6750

68-
const cartTotal = data?.cart?.total || '0';
69-
70-
const getUnitPrice = (subtotal: string, quantity: number) => {
71-
const numericSubtotal = parseFloat(subtotal.replace(/[^0-9.-]+/g, ''));
72-
return isNaN(numericSubtotal)
73-
? 'N/A'
74-
: (numericSubtotal / quantity).toFixed(2);
51+
// Update remote state in background
52+
updateCart({
53+
variables: {
54+
input: {
55+
clientMutationId: uuidv4(),
56+
items: [{
57+
key: cartKey,
58+
quantity: 0
59+
}],
60+
},
61+
},
62+
});
7563
};
7664

7765
return (
7866
<div className="container mx-auto px-4 py-8">
79-
{data?.cart?.contents?.nodes?.length ? (
67+
{cart?.products?.length ? (
8068
<>
8169
<div className="bg-white rounded-lg p-6 mb-8 md:w-full">
82-
{data.cart.contents.nodes.map((item: IProductRootObject) => (
70+
{cart.products.map((item: Product) => (
8371
<div
84-
key={item.key}
72+
key={item.cartKey}
8573
className="flex items-center border-b border-gray-200 py-4"
8674
>
8775
<div className="flex-shrink-0 w-24 h-24 relative hidden md:block">
8876
<Image
89-
src={
90-
item.product.node.image?.sourceUrl || '/placeholder.png'
91-
}
92-
alt={item.product.node.name}
77+
src={item.image?.sourceUrl || '/placeholder.png'}
78+
alt={item.name}
9379
layout="fill"
9480
objectFit="cover"
9581
className="rounded"
9682
/>
9783
</div>
9884
<div className="flex-grow ml-4">
9985
<h2 className="text-lg font-semibold">
100-
{item.product.node.name}
86+
{item.name}
10187
</h2>
10288
<p className="text-gray-600">
103-
kr {getUnitPrice(item.subtotal, item.quantity)}
89+
kr {item.price}
10490
</p>
10591
</div>
10692
<div className="flex items-center">
10793
<input
10894
type="number"
10995
min="1"
110-
value={item.quantity}
96+
value={item.qty}
11197
onChange={(event: ChangeEvent<HTMLInputElement>) => {
11298
const newQty = parseInt(event.target.value, 10);
11399
if (isNaN(newQty) || newQty < 1) return;
114100

115-
// Optimistically update local state
116-
if (cart) {
117-
const oldProduct = cart.products.find((p: Product) => p.cartKey === item.key);
118-
const oldQty = oldProduct?.qty || 0;
119-
const updatedProducts = cart.products.map((p: Product) =>
120-
p.cartKey === item.key ? { ...p, qty: newQty } : p
121-
);
122-
123-
// Calculate new total count
124-
const qtyDiff = newQty - oldQty;
125-
const newTotalCount = cart.totalProductsCount + qtyDiff;
126-
127-
setCart({
128-
...cart,
129-
products: updatedProducts,
130-
totalProductsCount: newTotalCount
131-
});
132-
}
101+
// Update local state
102+
useCartStore.getState().updateProductQuantity(item.cartKey, newQty);
133103

134104
// Update remote state in background
135105
handleQuantityChange(
136106
event,
137-
item.key,
138-
data.cart.contents.nodes,
107+
item.cartKey,
108+
newQty,
139109
updateCart
140110
);
141111
}}
142112
className="w-16 px-2 py-1 text-center border border-gray-300 rounded mr-2"
143113
/>
144114
<Button
145-
handleButtonClick={() =>
146-
handleRemoveProductClick(
147-
item.key,
148-
data.cart.contents.nodes,
149-
)
150-
}
115+
handleButtonClick={() => handleRemoveProductClick(item.cartKey)}
151116
variant="secondary"
152117
>
153118
Fjern
154119
</Button>
155120
</div>
156121
<div className="ml-4">
157-
<p className="text-lg font-semibold">{item.subtotal}</p>
122+
<p className="text-lg font-semibold">{item.totalPrice}</p>
158123
</div>
159124
</div>
160125
))}
161126
</div>
162127
<div className="bg-white rounded-lg p-6 md:w-full">
163128
<div className="flex justify-end mb-4">
164129
<span className="font-semibold pr-2">Subtotal:</span>
165-
<span>{cartTotal}</span>
130+
<span>{cart.totalProductsPrice}</span>
166131
</div>
167132
{!isCheckoutPage && (
168133
<div className="flex justify-center mb-4">

src/components/Header/Cart.component.tsx

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import { FC } from 'react';
22
import Link from 'next/link';
3+
import { useQuery } from '@apollo/client';
34

45
import useCartStore from '@/stores/cart';
6+
import { GET_CART } from '@/utils/gql/GQL_QUERIES';
7+
import { getFormattedCart } from '@/utils/functions/functions';
58

69
interface ICartProps {
710
stickyNav?: boolean;
@@ -12,8 +15,26 @@ interface ICartProps {
1215
* Displays amount of items in cart.
1316
*/
1417
const Cart: FC<ICartProps> = ({ stickyNav }) => {
15-
const { cart, isLoading } = useCartStore();
16-
const productCount = !isLoading ? cart?.totalProductsCount : undefined;
18+
const { cart, setCart, isLoading } = useCartStore();
19+
20+
const { loading: queryLoading, error } = useQuery(GET_CART, {
21+
notifyOnNetworkStatusChange: true,
22+
onCompleted: (data) => {
23+
const updatedCart = getFormattedCart(data);
24+
if (!cart || cart.totalProductsCount !== updatedCart?.totalProductsCount) {
25+
setCart(updatedCart || null);
26+
}
27+
},
28+
onError: (error) => {
29+
console.error('Error fetching cart:', error);
30+
// On error, we'll show the cached cart data instead of setting to null
31+
},
32+
// Fetch policy to ensure we get fresh data while respecting cache
33+
fetchPolicy: 'cache-and-network',
34+
});
35+
36+
// Use query loading state in combination with store loading state
37+
const productCount = (!isLoading && !queryLoading) ? cart?.totalProductsCount : undefined;
1738

1839
return (
1940
<>

src/components/Layout/Layout.component.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,15 @@ interface ILayoutProps {
2727
*/
2828

2929
const Layout = ({ children, title }: ILayoutProps) => {
30-
const { setCart } = useCartStore();
30+
const { cart, setCart } = useCartStore();
3131

3232
useQuery(GET_CART, {
3333
notifyOnNetworkStatusChange: true,
3434
onCompleted: (data) => {
3535
const updatedCart = getFormattedCart(data) as RootObject | undefined;
36-
setCart(updatedCart || null);
36+
if (!cart || cart.totalProductsCount !== updatedCart?.totalProductsCount) {
37+
setCart(updatedCart || null);
38+
}
3739
},
3840
});
3941

src/stores/cart.ts

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ interface CartState {
2828
isLoading: boolean;
2929
setCart: (cart: RootObject | null) => void;
3030
updateCart: (newCart: RootObject) => void;
31+
updateProductQuantity: (cartKey: string, newQty: number) => void;
32+
removeProduct: (cartKey: string) => void;
3133
}
3234

3335
type CartPersist = {
@@ -37,12 +39,51 @@ type CartPersist = {
3739
const useCartStore = create<CartState>()(
3840
persist(
3941
(set) => ({
40-
cart: null,
41-
isLoading: true,
42-
setCart: (cart: RootObject | null) => set({ cart, isLoading: false }),
43-
updateCart: (newCart: RootObject) => {
44-
set({ cart: newCart });
45-
}
42+
cart: null,
43+
isLoading: true,
44+
setCart: (cart: RootObject | null) => set({ cart, isLoading: false }),
45+
updateCart: (newCart: RootObject) => {
46+
set({ cart: newCart });
47+
},
48+
removeProduct: (cartKey: string) => {
49+
set((state) => {
50+
if (!state.cart) return state;
51+
52+
const updatedProducts = state.cart.products.filter(p => p.cartKey !== cartKey);
53+
const totalCount = updatedProducts.reduce((sum, product) => sum + product.qty, 0);
54+
55+
return {
56+
...state,
57+
cart: {
58+
...state.cart,
59+
products: updatedProducts,
60+
totalProductsCount: totalCount
61+
}
62+
};
63+
});
64+
},
65+
updateProductQuantity: (cartKey: string, newQty: number) => {
66+
set((state) => {
67+
if (!state.cart) return state;
68+
69+
const products = state.cart.products.map(product =>
70+
product.cartKey === cartKey
71+
? { ...product, qty: newQty }
72+
: product
73+
);
74+
75+
const totalCount = products.reduce((sum, product) => sum + product.qty, 0);
76+
77+
return {
78+
...state,
79+
cart: {
80+
...state.cart,
81+
products,
82+
totalProductsCount: totalCount
83+
}
84+
};
85+
});
86+
}
4687
}),
4788
{
4889
name: 'woocommerce-cart',

src/utils/functions/functions.tsx

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -288,26 +288,22 @@ export const getUpdatedItems = (
288288
export const handleQuantityChange = (
289289
event: ChangeEvent<HTMLInputElement>,
290290
cartKey: string,
291-
cart: IProductRootObject[],
291+
newQty: number,
292292
updateCart: (variables: IUpdateCartRootObject) => void,
293293
) => {
294294
if (process.browser) {
295295
event.stopPropagation();
296296

297-
// If the user tries to delete the count of product, set that to 1 by default ( This will not allow him to reduce it less than zero )
298-
const newQty = event.target.value ? parseInt(event.target.value, 10) : 1;
299-
300-
if (cart.length) {
301-
const updatedItems = getUpdatedItems(cart, newQty, cartKey);
302-
303-
updateCart({
304-
variables: {
305-
input: {
306-
clientMutationId: uuidv4(),
307-
items: updatedItems,
308-
},
297+
updateCart({
298+
variables: {
299+
input: {
300+
clientMutationId: uuidv4(),
301+
items: [{
302+
key: cartKey,
303+
quantity: newQty
304+
}],
309305
},
310-
});
311-
}
306+
},
307+
});
312308
}
313309
};

0 commit comments

Comments
 (0)