Skip to content

Commit 4d57cdb

Browse files
authored
Merge pull request #1434 from w3bdesign/1433-add-zustand-for-replacement-of-usestate
1433 add zustand for replacement of usestate
2 parents 0db0d3e + ae4f59c commit 4d57cdb

14 files changed

+2269
-1784
lines changed

package-lock.json

+2,017-1,472
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+9-7
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
"license": "ISC",
2525
"dependencies": {
2626
"@apollo/client": "^3.12.11",
27-
"@types/react": "^19.0.8",
2827
"algoliasearch": "^4.24.0",
2928
"autoprefixer": "^10.4.20",
3029
"framer-motion": "12.4.2",
@@ -37,19 +36,22 @@
3736
"react-dom": "18.3.1",
3837
"react-hook-form": "^7.54.2",
3938
"react-instantsearch-dom": "^6.40.4",
40-
"uuid": "^11.0.5"
39+
"uuid": "^11.0.5",
40+
"zustand": "^5.0.3"
4141
},
4242
"devDependencies": {
4343
"@playwright/test": "^1.50.1",
4444
"@types/lodash": "^4.17.15",
45-
"@types/node": "22.13.1",
45+
"@types/node": "^22.13.1",
4646
"@types/nprogress": "^0.2.3",
47+
"@types/react": "^19.0.8",
48+
"@types/react-dom": "^19.0.3",
4749
"@types/react-instantsearch-dom": "^6.12.8",
4850
"@types/uuid": "^10.0.0",
49-
"@typescript-eslint/eslint-plugin": "^8.24.0",
50-
"@typescript-eslint/parser": "^8.24.0",
51-
"babel-plugin-styled-components": "^2.1.4",
52-
"eslint-config-next": "^15.1.7",
51+
"@typescript-eslint/eslint-plugin": "^8.23.0",
52+
"@typescript-eslint/parser": "^8.23.0",
53+
"babel-plugin-styled-components": "^2.1.4",
54+
"eslint-config-next": "^15.1.6",
5355
"postcss-preset-env": "^10.1.3",
5456
"prettier": "^3.5.0",
5557
"tailwindcss": "^3.4.17",

src/components/Cart/CartContents.component.tsx

+47-89
Original file line numberDiff line numberDiff line change
@@ -1,157 +1,123 @@
1-
import { useContext, useEffect } from 'react';
1+
import { ChangeEvent } from 'react';
22
import { useMutation, useQuery } from '@apollo/client';
33
import Link from 'next/link';
44
import Image from 'next/image';
55
import { useRouter } from 'next/router';
66
import { v4 as uuidv4 } from 'uuid';
77

8-
import { CartContext } from '@/stores/CartProvider';
8+
import useCartStore, { RootObject, Product } from '@/stores/cart';
99
import Button from '@/components/UI/Button.component';
10-
import LoadingSpinner from '../LoadingSpinner/LoadingSpinner.component';
1110

1211
import {
1312
getFormattedCart,
14-
getUpdatedItems,
1513
handleQuantityChange,
16-
IProductRootObject,
1714
} from '@/utils/functions/functions';
1815

1916
import { GET_CART } from '@/utils/gql/GQL_QUERIES';
2017
import { UPDATE_CART } from '@/utils/gql/GQL_MUTATIONS';
2118

2219
const CartContents = () => {
2320
const router = useRouter();
24-
const { setCart } = useContext(CartContext);
21+
const { cart, setCart } = useCartStore();
2522
const isCheckoutPage = router.pathname === '/kasse';
2623

27-
const { data, refetch } = useQuery(GET_CART, {
24+
useQuery(GET_CART, {
2825
notifyOnNetworkStatusChange: true,
29-
onCompleted: () => {
30-
const updatedCart = getFormattedCart(data);
31-
if (!updatedCart && !data.cart.contents.nodes.length) {
32-
localStorage.removeItem('woocommerce-cart');
33-
setCart(null);
34-
return;
26+
onCompleted: (data) => {
27+
// Only update if there's a significant difference to avoid unnecessary re-renders
28+
const updatedCart = getFormattedCart(data) as RootObject | undefined;
29+
if (!cart || cart.totalProductsCount !== updatedCart?.totalProductsCount) {
30+
setCart(updatedCart || null);
3531
}
36-
localStorage.setItem('woocommerce-cart', JSON.stringify(updatedCart));
37-
setCart(updatedCart);
3832
},
3933
});
4034

41-
const [updateCart, { loading: updateCartProcessing }] = useMutation(
42-
UPDATE_CART,
43-
{
44-
onCompleted: () => {
45-
refetch();
46-
setTimeout(() => {
47-
refetch();
48-
}, 3000);
49-
},
50-
},
51-
);
52-
53-
const handleRemoveProductClick = (
54-
cartKey: string,
55-
products: IProductRootObject[],
56-
) => {
57-
if (products?.length) {
58-
const updatedItems = getUpdatedItems(products, 0, cartKey);
59-
updateCart({
60-
variables: {
61-
input: {
62-
clientMutationId: uuidv4(),
63-
items: updatedItems,
64-
},
65-
},
66-
});
67-
}
68-
refetch();
69-
setTimeout(() => {
70-
refetch();
71-
}, 3000);
72-
};
73-
74-
useEffect(() => {
75-
refetch();
76-
}, [refetch]);
35+
const [updateCart] = useMutation(UPDATE_CART);
7736

78-
const cartTotal = data?.cart?.total || '0';
37+
const handleRemoveProductClick = (cartKey: string) => {
38+
// Update local state
39+
useCartStore.getState().removeProduct(cartKey);
7940

80-
const getUnitPrice = (subtotal: string, quantity: number) => {
81-
const numericSubtotal = parseFloat(subtotal.replace(/[^0-9.-]+/g, ''));
82-
return isNaN(numericSubtotal)
83-
? 'N/A'
84-
: (numericSubtotal / quantity).toFixed(2);
41+
// Update remote state in background
42+
updateCart({
43+
variables: {
44+
input: {
45+
clientMutationId: uuidv4(),
46+
items: [{
47+
key: cartKey,
48+
quantity: 0
49+
}],
50+
},
51+
},
52+
});
8553
};
8654

8755
return (
8856
<div className="container mx-auto px-4 py-8">
89-
{data?.cart?.contents?.nodes?.length ? (
57+
{cart?.products?.length ? (
9058
<>
9159
<div className="bg-white rounded-lg p-6 mb-8 md:w-full">
92-
{data.cart.contents.nodes.map((item: IProductRootObject) => (
60+
{cart.products.map((item: Product) => (
9361
<div
94-
key={item.key}
62+
key={item.cartKey}
9563
className="flex items-center border-b border-gray-200 py-4"
9664
>
9765
<div className="flex-shrink-0 w-24 h-24 relative hidden md:block">
9866
<Image
99-
src={
100-
item.product.node.image?.sourceUrl || '/placeholder.png'
101-
}
102-
alt={item.product.node.name}
67+
src={item.image?.sourceUrl || '/placeholder.png'}
68+
alt={item.name}
10369
layout="fill"
10470
objectFit="cover"
10571
className="rounded"
10672
/>
10773
</div>
10874
<div className="flex-grow ml-4">
10975
<h2 className="text-lg font-semibold">
110-
{item.product.node.name}
76+
{item.name}
11177
</h2>
11278
<p className="text-gray-600">
113-
kr {getUnitPrice(item.subtotal, item.quantity)}
79+
kr {item.price}
11480
</p>
11581
</div>
11682
<div className="flex items-center">
11783
<input
11884
type="number"
11985
min="1"
120-
value={item.quantity}
121-
onChange={(event) => {
86+
value={item.qty}
87+
onChange={(event: ChangeEvent<HTMLInputElement>) => {
88+
const newQty = parseInt(event.target.value, 10);
89+
if (isNaN(newQty) || newQty < 1) return;
90+
91+
// Update local state
92+
useCartStore.getState().updateProductQuantity(item.cartKey, newQty);
93+
94+
// Update remote state in background
12295
handleQuantityChange(
12396
event,
124-
item.key,
125-
data.cart.contents.nodes,
126-
updateCart,
127-
updateCartProcessing,
97+
item.cartKey,
98+
newQty,
99+
updateCart
128100
);
129101
}}
130102
className="w-16 px-2 py-1 text-center border border-gray-300 rounded mr-2"
131103
/>
132104
<Button
133-
handleButtonClick={() =>
134-
handleRemoveProductClick(
135-
item.key,
136-
data.cart.contents.nodes,
137-
)
138-
}
105+
handleButtonClick={() => handleRemoveProductClick(item.cartKey)}
139106
variant="secondary"
140-
buttonDisabled={updateCartProcessing}
141107
>
142108
Fjern
143109
</Button>
144110
</div>
145111
<div className="ml-4">
146-
<p className="text-lg font-semibold">{item.subtotal}</p>
112+
<p className="text-lg font-semibold">{item.totalPrice}</p>
147113
</div>
148114
</div>
149115
))}
150116
</div>
151117
<div className="bg-white rounded-lg p-6 md:w-full">
152118
<div className="flex justify-end mb-4">
153119
<span className="font-semibold pr-2">Subtotal:</span>
154-
<span>{cartTotal}</span>
120+
<span>{cart.totalProductsPrice}</span>
155121
</div>
156122
{!isCheckoutPage && (
157123
<div className="flex justify-center mb-4">
@@ -172,14 +138,6 @@ const CartContents = () => {
172138
</Link>
173139
</div>
174140
)}
175-
{updateCartProcessing && (
176-
<div className="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50">
177-
<div className="bg-white p-4 rounded-lg">
178-
<p className="text-lg mb-2">Oppdaterer handlekurv...</p>
179-
<LoadingSpinner />
180-
</div>
181-
</div>
182-
)}
183141
</div>
184142
);
185143
};

src/components/Checkout/CheckoutForm.component.tsx

+10-31
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*eslint complexity: ["error", 20]*/
22
// Imports
3-
import { useState, useContext, useEffect } from 'react';
3+
import { useState, useEffect } from 'react';
44
import { useQuery, useMutation, ApolloError } from '@apollo/client';
55

66
// Components
@@ -11,7 +11,7 @@ import LoadingSpinner from '../LoadingSpinner/LoadingSpinner.component';
1111
// GraphQL
1212
import { GET_CART } from '@/utils/gql/GQL_QUERIES';
1313
import { CHECKOUT_MUTATION } from '@/utils/gql/GQL_MUTATIONS';
14-
import { CartContext } from '@/stores/CartProvider';
14+
import useCartStore, { RootObject } from '@/stores/cart';
1515

1616
// Utils
1717
import {
@@ -51,29 +51,17 @@ export interface ICheckoutData {
5151
}
5252

5353
const CheckoutForm = () => {
54-
const { cart, setCart } = useContext(CartContext);
54+
const { cart, setCart } = useCartStore();
5555
const [orderData, setOrderData] = useState<ICheckoutData | null>(null);
5656
const [requestError, setRequestError] = useState<ApolloError | null>(null);
5757
const [orderCompleted, setorderCompleted] = useState<boolean>(false);
5858

5959
// Get cart data query
60-
const { data, refetch } = useQuery(GET_CART, {
60+
useQuery(GET_CART, {
6161
notifyOnNetworkStatusChange: true,
62-
onCompleted: () => {
63-
// Update cart in the localStorage.
64-
const updatedCart = getFormattedCart(data);
65-
66-
if (!updatedCart && !data.cart.contents.nodes.length) {
67-
localStorage.removeItem('woo-session');
68-
localStorage.removeItem('wooocommerce-cart');
69-
setCart(null);
70-
return;
71-
}
72-
73-
localStorage.setItem('woocommerce-cart', JSON.stringify(updatedCart));
74-
75-
// Update cart data in React Context.
76-
setCart(updatedCart);
62+
onCompleted: (data) => {
63+
const updatedCart = getFormattedCart(data) as RootObject | undefined;
64+
setCart(updatedCart || null);
7765
},
7866
});
7967

@@ -84,16 +72,14 @@ const CheckoutForm = () => {
8472
variables: {
8573
input: orderData,
8674
},
75+
refetchQueries: [{ query: GET_CART }],
76+
awaitRefetchQueries: true,
8777
onCompleted: () => {
88-
localStorage.removeItem('woo-session');
89-
localStorage.removeItem('wooocommerce-cart');
9078
setorderCompleted(true);
9179
setCart(null);
92-
refetch();
9380
},
9481
onError: (error) => {
9582
setRequestError(error);
96-
refetch();
9783
},
9884
},
9985
);
@@ -102,15 +88,8 @@ const CheckoutForm = () => {
10288
if (null !== orderData) {
10389
// Perform checkout mutation when the value for orderData changes.
10490
checkout();
105-
setTimeout(() => {
106-
refetch();
107-
}, 2000);
10891
}
109-
}, [checkout, orderData, refetch]);
110-
111-
useEffect(() => {
112-
refetch();
113-
}, [refetch]);
92+
}, [checkout, orderData]);
11493

11594
const handleFormSubmit = (submitData: ICheckoutDataProps) => {
11695
const checkOutData = createCheckoutData(submitData);

0 commit comments

Comments
 (0)