react - nextjs - tailwind - sanity
I'm encountering an issue in my React application where the state within a Context is not consistently updating. Specifically, I'm using useStateContext to access the context's state, and after successfully updating the selected variant, useStateContext returns null. The relevant code snippets are provided below:
its live on https://reanalog-nextjs-project.vercel.app/product/boardmen after add to cart , i dont see the product in the cartpage, please anybody can help ?
(StateContext.js): import React, { createContext, useContext, useState, useEffect } from 'react'; import { toast } from 'react-hot-toast'; const Context = createContext(); export const StateContext = ({ children }) => { console.log('StateContext wird aufgerufen'); const [showCart, setShowCart] = useState(false); const [cartItems, setCartItems] = useState([]); const [totalPrice, setTotalPrice] = useState(0); const [totalQuantities, setTotalQuantities] = useState(0); const [qty, setQty] = useState(1); const [selectedVariant, setSelectedVariant] = useState(null); let foundProduct; let index; const updateSelectedVariant = (variant) => { setSelectedVariant(variant); }; const onAdd = (variant, product, quantity) => { if (!variant) { // Handle case where variant is not defined return; } setCartItems(prevItems => [...prevItems, {variant, product, quantity}]); const checkProductInCart = cartItems.find((item) => item._id === product._id); setTotalPrice((prevTotalPrice) => prevTotalPrice + variant.price * quantity); setTotalQuantities((prevTotalQuantities) => prevTotalQuantities + quantity); if (checkProductInCart) { const updatedCartItems = cartItems.map((cartProduct) => { if (cartProduct._id === product._id && cartProduct.variant._key === variant._key) { return { ...cartProduct, quantity: cartProduct.quantity + quantity }; } return cartProduct; }); setCartItems(updatedCartItems); } else { // Initialisiere `product` als Objekt const newProduct = { ...product, quantity: quantity, variant: variant }; setCartItems(prevItems => [...prevItems, newProduct]); } toast.success(`${quantity} ${product.name} (${variant.size}) wurde dem Warenkorb hinzugefügt.`); }; const onRemove = (product) => { foundProduct = cartItems.find((item) => item._id === product._id); const newCartItems = cartItems.filter((item) => item._id !== product._id); setTotalPrice((prevTotalPrice) => prevTotalPrice -foundProduct.price * foundProduct.quantity); setTotalQuantities(prevTotalQuantities => prevTotalQuantities - foundProduct.quantity); setCartItems(newCartItems); } const toggleCartItemQuanitity = (id, value, variant) => { foundProduct = cartItems.find((item) => item._id === id); index = cartItems.findIndex((product) => product._id === id); const newCartItems = cartItems.filter((item) => item._id !== id); if (foundProduct.variant._key === selectedVariant._key) { if (value === 'inc') { setCartItems([...newCartItems, { ...foundProduct, quantity: foundProduct.quantity + 1 }]); setTotalPrice((prevTotalPrice) => prevTotalPrice + foundProduct.variant.price); setTotalQuantities((prevTotalQuantities) => prevTotalQuantities + 1); } else if (value === 'dec') { if (foundProduct.quantity > 1) { setCartItems([...newCartItems, { ...foundProduct, quantity: foundProduct.quantity - 1 }]); setTotalPrice((prevTotalPrice) => prevTotalPrice - foundProduct.variant.price); setTotalQuantities((prevTotalQuantities) => prevTotalQuantities - 1); } } } }; const incQty = () => { setQty((prevQty) => prevQty + 1); } const decQty = () => { setQty((prevQty) => { if(prevQty - 1 < 1) return 1; return prevQty - 1; }); } return ( <Context.Provider value={{ showCart, setShowCart, cartItems, totalPrice, totalQuantities, qty, incQty, decQty, onAdd, toggleCartItemQuanitity, onRemove, setCartItems, setTotalPrice, setTotalQuantities, selectedVariant, updateSelectedVariant, }} > {children} </Context.Provider> ) } export const useStateContext = () => useContext(Context);
(ProductDetails.js): [slug].js
import React, { useState } from "react";
import { Disclosure, RadioGroup, Tab } from "@headlessui/react";
import { HeartIcon } from "@heroicons/react/24/outline";
import { Product } from "../../components";
import { client, urlFor } from "../../lib/client";
import { useStateContext } from "../../context/StateContext";
const ProductDetails = ({ product, products }) => {
const {
image,
name,
details,
price,
beschreibung,
edition,
technik,
jahr,
variants,
} = product;
const { decQty, incQty, qty, onAdd, setShowCart, toggleCartItemQuanitity } = useStateContext();
const [selectedVariant, setSelectedVariant] = useState(variants && variants.length > 0 ? variants[0] : null);
const handleAddToCart = () => {
onAdd(selectedVariant, product, qty);
};
const handleToggleCartItemQuantity = (id, value) => {
toggleCartItemQuanitity(id, value, selectedVariant);
};
const handleVariantChange = (variant) => {
setSelectedVariant(variant);
};
const [index, setIndex] = useState(0);
console.log("Ausgewählte Variante:", selectedVariant);
return (
<div className="bg-grey xl:w-[1200px] mx-auto">
<div className="mx-auto max-w-2xl px-4 py-16 sm:px-6 sm:py-24 lg:px-0 lg:max-w-7xl">
<div className="lg:grid lg:grid-cols-2 lg:items-start lg:gap-x-8">
{/* Image gallery */}
<Tab.Group as="div" className="flex flex-col-reverse">
{/* Image selector */}
<div className="mx-auto mt-6 w-full max-w-2xl sm:block lg:max-w-none">
<Tab.List className="grid grid-cols-4 gap-6">
{image?.map((item, i) => (
<Tab
key={item._key}
className="relative flex h-24 cursor-pointer items-center justify-center
bg-white text-sm font-medium uppercase text-gray-900 hover:bg-gray-50
"
>
{({ selected }) => (
<>
<span className="sr-only">{image.name}</span>
<span className="absolute inset-0 overflow-hidden">
<img
src={urlFor(item)}
alt=""
className="h-full w-full object-cover object-center"
/>
</span>
<span />
</>
)}
</Tab>
))}
</Tab.List>
</div>
<Tab.Panels className="aspect-h-1 aspect-w-1 w-full">
{image?.map((item, i) => (
<Tab.Panel key={item._key}>
<img
src={urlFor(item)}
alt={image.alt}
className="h-full w-full object-cover object-center "
/>
</Tab.Panel>
))}
</Tab.Panels>
</Tab.Group>
{/* Product info */}
<div className="mt-10 px-4 sm:mt-16 sm:px-0 lg:mt-0">
<div className="flex justify-between mt">
<h1 className="text-3xl font-bold tracking-tight text-black">
{name}
</h1>
<img src="\icons\HerzKonturreact.svg" />
</div>
<div className="flex">
<div className="mt-10">
<p>Edition</p>
<p>Edition</p>
<p>Edition</p>
</div>
<div className="mt-10 ml-7">
<p>{edition}</p>
<p>{technik}</p>
<p>{jahr}</p>
</div>
</div>
<div className="mt-6">
<h3 className="sr-only">Description</h3>
<div className="space-y-6 text-black" /> <p>{beschreibung}</p>
</div>
<form className="mt-6">
Colors
{/* <div>
<h3 className="text-sm text-gray-600">Color</h3>
<RadioGroup value={selectedColor} onChange={setSelectedColor} className="mt-2">
<RadioGroup.Label className="sr-only">Choose a color</RadioGroup.Label>
<span className="flex items-center space-x-3">
{product.colors.map((color) => (
<RadioGroup.Option
key={color.name}
value={color}
className={({ active, checked }) =>
classNames(
color.selectedColor,
active && checked ? 'ring ring-offset-1' : '',
!active && checked ? 'ring-2' : '',
'relative -m-0.5 flex cursor-pointer items-center justify-center rounded-full p-0.5 focus:outline-none'
)
}
>
<RadioGroup.Label as="span" className="sr-only">
{color.name}
</RadioGroup.Label>
<span
aria-hidden="true"
className={classNames(
color.bgColor,
'h-8 w-8 rounded-full border border-black border-opacity-10'
)}
/>
</RadioGroup.Option>
))}
</span>
</RadioGroup>
</div> */}
<div className="sm:flex-col1 mt-10 flex">
<button
type="button"
className="ml-4 flex items-center justify-center rounded-md px-3 py-3 text-black hover:bg-gray-100 hover:text-gray-500"
>
<HeartIcon
className="h-6 w-6 flex-shrink-0"
aria-hidden="true"
/>
<span className="sr-only">Add to favorites</span>
</button>
</div>
</form>
<div className="mt-10">
<button
className="relative bg-white z-0 border-2 border-black bg-transparent py-2.5 px-5 font-medium
uppercase text-black transition-colors hover:text-white before:absolute
before:left-0 before:bottom-0 before:-z-10 before:h-full before:w-full before:origin-bottom-left
before:scale-y-0 before:bg-black before:transition-transform before:duration-300 before:content-['']
before:hover:scale-y-100 hover:z-50"
>
90 X 90
</button>
</div>
<div></div>
{/* Varianten-Buttons mit Preisen */}
<div className="mt-6">
<h3 className="text-lg font-bold mb-2">Verfügbare Varianten:</h3>
<div className="flex space-x-4">
{variants && variants.map((variant) => (
<button
key={variant._key}
onClick={() => handleVariantChange(variant)}
className={`px-4 py-2 border ${
variant._key === selectedVariant?._key
? "border-blue-500 bg-blue-500 text-white"
: "border-gray-300 hover:border-gray-500"
}`}
>
<div className="flex flex-col items-center">
<span>{variant.size}</span>
</div>
</button>
))}
</div>
</div>
<div className="mt-3">
<h2 className="sr-only">Product information</h2>
<p className="text-3xl tracking-tight text-gray-900">
{selectedVariant && (
<span className="mt-4">${selectedVariant.price},00 €</span>
)}
</p>
</div>
<div className="mt-10">
<button
className="relative bg-white z-0 border-2 border-black bg-transparent py-2.5 px-5 font-medium
uppercase text-black transition-colors hover:text-white before:absolute
before:left-0 before:bottom-0 before:-z-10 before:h-full before:w-full before:origin-bottom-left
before:scale-y-0 before:bg-black before:transition-transform before:duration-300 before:content-['']
before:hover:scale-y-100 hover:z-50"
onClick={handleAddToCart}
>
ADD TO CART
</button>
</div>
</div>
</div>
</div>
<div className="bg-white mx-auto mt-28 xl:w-[1200px]">
<p>WEITERES AUS DIESER KOLLEKTION</p>
<div className="mx-auto overflow-hidden py-16 sm:py-24">
<div className="grid grid-cols-1 gap-x-6 gap-y-10 md:grid-cols-2 lg:grid-cols-3 lg:gap-x-8">
{products?.map((product) => (
<Product key={product._id} product={product} />
))}
</div>
</div>
</div>
</div>
);
};
export const getStaticPaths = async () => {
const query = `*[_type == "product"] {
slug {
current
}
}`;
const products = await client.fetch(query);
const paths = products.map((product) => ({
params: {
slug: product.slug.current,
},
}));
return {
paths,
fallback: "blocking",
};
};
export const getStaticProps = async ({ params: { slug }}) => {
const query = `*[_type == "product" && slug.current == '${slug}'][0]`;
const productsQuery = '*[_type == "product"]'
const product = await client.fetch(query);
const products = await client.fetch(productsQuery);
console.log("ausgewahltes produkt", product);
return {
props: { products, product }
}
}
export default ProductDetails;
CartPage cart.js
import Link from "next/link";
import React, { useRef } from "react";
import { CheckIcon, ClockIcon } from "@heroicons/react/20/solid";
import toast from "react-hot-toast";
import { useStateContext } from "../context/StateContext";
import { urlFor } from "@/lib/client";
function CartPage() {
const cartRef = useRef();
const { totalPrice, totalQuantities, cartItems, setShowCart, toggleCartItemQuanitity, onRemove, updateSelectedVariant, selectedVariant } =
useStateContext();
const handleVariantChange = (variants) => {
updateSelectedVariant(variants);
};
console.log("Ausgabe von useStateContext:", selectedVariant);
return (
<div className="bg-white">
<div className="mx-auto w-[1200px] py-16 font-primary sm:py-24 ">
<h1 className="text-3xl font-bold tracking-tight text-gray-900">
CART
</h1>
{selectedVariant && (
<p>
Ausgewählte Variante: {selectedVariant.size}, Preis:{" "}
{selectedVariant.price}
</p>
)}
({totalQuantities} Artikel)
<form className="mt-12">
<div>
<h2 className="sr-only">Artikel in deinem Warenkorb</h2>
<div className="product-container">
{cartItems.length >= 1 &&
cartItems.map((item) => (
<div className="product" key={item._id}>
<img src={urlFor(item?.image[0])} className="cart-product-image" />
<div className="item-desc">
<div className="flex top">
<h5>{item.name}</h5>
{item.variants && <span>{item.variants.size}</span>}
<h4>${item.price}</h4>
</div>
<div className="flex bottom">
<div>
<p className="quantity-desc">
<span className="minus" onClick={() => toggleCartItemQuanitity(item._id, 'dec')}>
<AiOutlineMinus />
</span>
<span className="num">{item.quantity}</span>
<span className="plus" onClick={() => toggleCartItemQuanitity(item._id, 'inc')}><AiOutlinePlus /></span>
</p>
</div>
<button
type="button"
className="remove-item"
onClick={() => onRemove(item)}
>
<TiDeleteOutline />
</button>
</div>
</div>
</div>
))}
</div>
</div>
{/* Zusammenfassung der Bestellung */}
<div className="mt-10 w-[1200px]">
<div className="rounded-lg bg-gray-50 px-4 py-6 sm:p-6 lg:p-8">
<h2 className="sr-only">Zusammenfassung der Bestellung</h2>
<div className="flow-root">
<dl className="-my-4 divide-y divide-gray-200 text-sm">
<div className="flex items-center justify-between py-4">
<dt className="text-gray-600">Zwischensumme</dt>
<dd className="font-medium text-gray-900">${totalPrice.toFixed(2)}</dd>
</div>
{/* ... Weitere Details zu Versand und Steuern */}
<div className="flex items-center justify-between py-4">
<dt className="text-base font-medium text-gray-900">
Gesamtsumme
</dt>
<dd className="text-base font-medium text-gray-900">
${totalPrice.toFixed(2)}
</dd>
</div>
</dl>
</div>
</div>
<div className="mt-10">
<button
type="submit"
className="w-full rounded-md border border-transparent bg-black px-4 py-3 text-base font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 focus:ring-offset-gray-50"
>
Zur Kasse
</button>
</div>
<div className="mt-6 text-center text-sm text-gray-500">
<p>
oder
<a
href="#"
className="font-medium text-indigo-600 hover:text-indigo-500"
>
Weiter Einkaufen
<span aria-hidden="true"> →</span>
</a>
</p>
</div>
</div>
</form>
</div>
</div>
);
}
export default CartPage;
its live on https://reanalog-nextjs-project.vercel.app/product/boardmen after add to cart , i dont see the product in the cartpage, please anybody can help ?