Skip to content

Commit 47de5b6

Browse files
committed
Zustand
1 parent d0e6492 commit 47de5b6

File tree

12 files changed

+9537
-222
lines changed

12 files changed

+9537
-222
lines changed

package-lock.json

Lines changed: 9405 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 2 deletions
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",
@@ -43,8 +42,10 @@
4342
"devDependencies": {
4443
"@playwright/test": "^1.50.1",
4544
"@types/lodash": "^4.17.15",
46-
"@types/node": "22.13.1",
45+
"@types/node": "^22.13.1",
4746
"@types/nprogress": "^0.2.3",
47+
"@types/react": "^19.0.8",
48+
"@types/react-dom": "^19.0.3",
4849
"@types/react-instantsearch-dom": "^6.12.8",
4950
"@types/uuid": "^10.0.0",
5051
"@typescript-eslint/eslint-plugin": "^8.23.0",

src/components/Cart/CartContents.component.tsx

Lines changed: 38 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
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';
1010
import LoadingSpinner from '../LoadingSpinner/LoadingSpinner.component';
1111

@@ -21,32 +21,22 @@ import { UPDATE_CART } from '@/utils/gql/GQL_MUTATIONS';
2121

2222
const CartContents = () => {
2323
const router = useRouter();
24-
const { setCart } = useContext(CartContext);
24+
const { cart, setCart } = useCartStore();
2525
const isCheckoutPage = router.pathname === '/kasse';
2626

27-
const { data, refetch } = useQuery(GET_CART, {
27+
const { data } = useQuery(GET_CART, {
2828
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;
35-
}
36-
localStorage.setItem('woocommerce-cart', JSON.stringify(updatedCart));
37-
setCart(updatedCart);
29+
onCompleted: (data) => {
30+
const updatedCart = getFormattedCart(data) as RootObject | undefined;
31+
setCart(updatedCart || null);
3832
},
3933
});
4034

4135
const [updateCart, { loading: updateCartProcessing }] = useMutation(
4236
UPDATE_CART,
4337
{
44-
onCompleted: () => {
45-
refetch();
46-
setTimeout(() => {
47-
refetch();
48-
}, 3000);
49-
},
38+
refetchQueries: [{ query: GET_CART }],
39+
awaitRefetchQueries: true,
5040
},
5141
);
5242

@@ -55,6 +45,18 @@ const CartContents = () => {
5545
products: IProductRootObject[],
5646
) => {
5747
if (products?.length) {
48+
// Optimistically update local state
49+
const currentCart = cart;
50+
if (currentCart) {
51+
const updatedProducts = currentCart.products.filter((p: Product) => p.cartKey !== cartKey);
52+
setCart({
53+
...currentCart,
54+
products: updatedProducts,
55+
totalProductsCount: currentCart.totalProductsCount - 1
56+
});
57+
}
58+
59+
// Update remote state in background
5860
const updatedItems = getUpdatedItems(products, 0, cartKey);
5961
updateCart({
6062
variables: {
@@ -64,17 +66,10 @@ const CartContents = () => {
6466
},
6567
},
6668
});
69+
6770
}
68-
refetch();
69-
setTimeout(() => {
70-
refetch();
71-
}, 3000);
7271
};
7372

74-
useEffect(() => {
75-
refetch();
76-
}, [refetch]);
77-
7873
const cartTotal = data?.cart?.total || '0';
7974

8075
const getUnitPrice = (subtotal: string, quantity: number) => {
@@ -118,7 +113,22 @@ const CartContents = () => {
118113
type="number"
119114
min="1"
120115
value={item.quantity}
121-
onChange={(event) => {
116+
onChange={(event: ChangeEvent<HTMLInputElement>) => {
117+
const newQty = parseInt(event.target.value, 10);
118+
if (isNaN(newQty) || newQty < 1) return;
119+
120+
// Optimistically update local state
121+
if (cart) {
122+
const updatedProducts = cart.products.map((p: Product) =>
123+
p.cartKey === item.key ? { ...p, qty: newQty } : p
124+
);
125+
setCart({
126+
...cart,
127+
products: updatedProducts,
128+
});
129+
}
130+
131+
// Update remote state in background
122132
handleQuantityChange(
123133
event,
124134
item.key,

src/components/Checkout/CheckoutForm.component.tsx

Lines changed: 10 additions & 27 deletions
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,21 @@ 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+
const { data } = useQuery(GET_CART, {
6161
notifyOnNetworkStatusChange: true,
6262
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');
63+
const updatedCart = getFormattedCart(data) as RootObject | undefined;
64+
if (!updatedCart || !data.cart.contents.nodes.length) {
6965
setCart(null);
7066
return;
7167
}
72-
73-
localStorage.setItem('woocommerce-cart', JSON.stringify(updatedCart));
74-
75-
// Update cart data in React Context.
76-
setCart(updatedCart);
68+
setCart(updatedCart as RootObject);
7769
},
7870
});
7971

@@ -84,16 +76,14 @@ const CheckoutForm = () => {
8476
variables: {
8577
input: orderData,
8678
},
79+
refetchQueries: [{ query: GET_CART }],
80+
awaitRefetchQueries: true,
8781
onCompleted: () => {
88-
localStorage.removeItem('woo-session');
89-
localStorage.removeItem('wooocommerce-cart');
9082
setorderCompleted(true);
9183
setCart(null);
92-
refetch();
9384
},
9485
onError: (error) => {
9586
setRequestError(error);
96-
refetch();
9787
},
9888
},
9989
);
@@ -102,15 +92,8 @@ const CheckoutForm = () => {
10292
if (null !== orderData) {
10393
// Perform checkout mutation when the value for orderData changes.
10494
checkout();
105-
setTimeout(() => {
106-
refetch();
107-
}, 2000);
10895
}
109-
}, [checkout, orderData, refetch]);
110-
111-
useEffect(() => {
112-
refetch();
113-
}, [refetch]);
96+
}, [checkout, orderData]);
11497

11598
const handleFormSubmit = (submitData: ICheckoutDataProps) => {
11699
const checkOutData = createCheckoutData(submitData);

src/components/Header/Cart.component.tsx

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { useContext, useState, useEffect } from 'react';
1+
import { FC } from 'react';
22
import Link from 'next/link';
33

4-
import { CartContext } from '@/stores/CartProvider';
4+
import useCartStore from '@/stores/cart';
55

66
interface ICartProps {
77
stickyNav?: boolean;
@@ -11,17 +11,9 @@ interface ICartProps {
1111
* Displays the shopping cart contents.
1212
* Displays amount of items in cart.
1313
*/
14-
const Cart = ({ stickyNav }: ICartProps) => {
15-
const { cart } = useContext(CartContext);
16-
const [productCount, setProductCount] = useState<number | null | undefined>();
17-
18-
useEffect(() => {
19-
if (cart) {
20-
setProductCount(cart.totalProductsCount);
21-
} else {
22-
setProductCount(null);
23-
}
24-
}, [cart]);
14+
const Cart: FC<ICartProps> = ({ stickyNav }) => {
15+
const { cart, isLoading } = useCartStore();
16+
const productCount = !isLoading ? cart?.totalProductsCount : undefined;
2517

2618
return (
2719
<>
@@ -48,14 +40,14 @@ const Cart = ({ stickyNav }: ICartProps) => {
4840
</span>
4941
</Link>
5042

51-
{productCount && (
43+
{productCount ? (
5244
<span
5345
className={`w-6 h-6 pb-2 -mt-5 !-ml-2 text-center rounded-full
5446
${stickyNav ? 'text-black bg-white' : 'text-white bg-black'}`}
5547
>
5648
{productCount}
5749
</span>
58-
)}
50+
) : null}
5951
</>
6052
);
6153
};

src/components/Layout/Layout.component.tsx

Lines changed: 7 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,11 @@
1-
// Imports
2-
import { ReactNode, useContext, useEffect } from 'react';
1+
import { ReactNode } from 'react';
32
import { useQuery } from '@apollo/client';
43

5-
// Components
64
import Header from '@/components/Header/Header.component';
75
import PageTitle from './PageTitle.component';
86
import Footer from '@/components/Footer/Footer.component';
97
import Stickynav from '@/components/Footer/Stickynav.component';
10-
11-
// State
12-
import { CartContext } from '@/stores/CartProvider';
8+
import useCartStore, { RootObject } from '@/stores/cart';
139

1410
// Utils
1511
import { getFormattedCart } from '@/utils/functions/functions';
@@ -31,30 +27,16 @@ interface ILayoutProps {
3127
*/
3228

3329
const Layout = ({ children, title }: ILayoutProps) => {
34-
const { setCart } = useContext(CartContext);
30+
const { setCart } = useCartStore();
3531

36-
const { data, refetch } = useQuery(GET_CART, {
32+
useQuery(GET_CART, {
3733
notifyOnNetworkStatusChange: true,
38-
onCompleted: () => {
39-
// Update cart in the localStorage.
40-
const updatedCart = getFormattedCart(data);
41-
42-
if (!updatedCart && !data?.cart?.contents?.nodes.length) {
43-
// Should we clear the localStorage if we have no remote cart?
44-
return;
45-
}
46-
47-
localStorage.setItem('woocommerce-cart', JSON.stringify(updatedCart));
48-
49-
// Update cart data in React Context.
50-
setCart(updatedCart);
34+
onCompleted: (data) => {
35+
const updatedCart = getFormattedCart(data) as RootObject | undefined;
36+
setCart(updatedCart || null);
5137
},
5238
});
5339

54-
useEffect(() => {
55-
refetch();
56-
}, [refetch]);
57-
5840
return (
5941
<div className="flex flex-col min-h-screen w-full mx-auto">
6042
<Header title={title} />

0 commit comments

Comments
 (0)