Next.js Discord

Discord Forum

Properly Re-rendering elements without hydrating issues or recursive loops

Unanswered
ihyd posted this in #help-forum
Open in Discord
I've run into the issue of improperly rendering elements and I don't know how to dig myself out of this hole.

  const CartItem: React.FC<CartItemProps> = ({ name, image, price, quantity, id, removeFromCart }) => {
    const [currentQuantity, setQuantity] = useState(quantity);
  
    useEffect(() => {
      setQuantity(quantity);
    }, [quantity]);
  
    const handleQuantityChange = (newQuantity: number) => {
      setQuantity(newQuantity);
      updateCartItem(id, newQuantity);
    };
  
    return (
      <span className="flex flex-row p-4 m-4 bg-primary-1000 rounded-lg">
        <Image src={image} width={150} height={150} alt="Cart Item Image" />
        <span className="p-4 pt-1 flex flex-col justify-center items-center gap-2">
          <p className="font-bold top-0 p-2">{name}</p>
          <p>${price}</p>
        </span>
        <span className="flex flex-row justify-center items-center gap-4">
          <Quantity
            quantity={currentQuantity}
            lowerLimit={1}
            upperLimit={99}
            increase={() => handleQuantityChange(currentQuantity + 1)}
            decrease={() => handleQuantityChange(currentQuantity - 1)}
          />
          <p>Total: {formatCurrency(currentQuantity * price)}</p>
        </span>
        <button onClick={() => removeFromCart(id)}>Remove</button>
      </span>
    );
  };


  const YourCart = () => {
    const [cartItems, setCartItems] = useState<CartItemProps[]>([]);
    const [loading, setLoading] = useState(true);
    const [total, setTotal] = useState(0);
  
    useEffect(() => {
      const fetchCartItems = async () => {
        try {
          const items = await getCart();
          setCartItems(items);
          setLoading(false);
        } catch (error) {
          console.error('Error fetching cart items:', error);
          setLoading(false);
        }
      };
  
      fetchCartItems();
    }, []);
  
    useEffect(() => {
      const updateTotal = async () => {
        try {
          const cartTotal = await getCartTotal();
          setTotal(cartTotal);
        } catch (error) {
          console.error('Error updating total:', error);
        }
      };
  
      updateTotal();
    }, [cartItems]);
  
    const handleRemoveFromCart = async (itemId: number) => {
      try {
        removeCartItem(itemId);
        const updatedCartItems = cartItems.filter(item => item.id !== itemId);
        setCartItems(updatedCartItems);
      } catch (error) {
        console.error('Error removing item from cart:', error);
      }
    };
  
    return (
      <div className="flex flex-row">
        <div className="flex flex-col">
          <span className="text-lg p-2 text-center mt-4 flex flex-col">
            Your cart ({cartItems.length} items)
          </span>
          {loading ? (
            <Loader />
          ) : (
            cartItems.map((item) => (
              <CartItem
                key={item.id}
                name={item.name}
                image={item.image}
                price={item.price}
                quantity={item.quantity}
                id={item.id}
                removeFromCart={handleRemoveFromCart}
              />
            ))
          )}
        </div>
        <CheckOut total={total} />
      </div>
    );
  };
  
  
  export default YourCart;
  


This is the full component, I want the total in CheckOut to be changed, and that component re-rendered anytime the quantity changes, or a cart item is added / removed, how can I get this done here? and what am I doing wrong

39 Replies

sorry for the messy code
I can post the dependencies such as the cart commands, but it uses local storage
bump, still need help
can you help me simplify your code
and remove irrelevant codes
@aardani can you help me simplify your code
the issue is I don't know what exactly I need to restructure to get this working as intended
and next.js is driving me crazy
I can't get my API routes working
@ihyd the issue is I don't know what exactly I need to restructure to get this working as intended
remove components one by one until the error dissapears
@aardani remove components one by one until the error dissapears
there isn't an error, I want to know how I can properly structure it so it only re-renders certain elements at certain points, like the thought process behind someone who's more advanced in next.js, how would they structure this
because I probably next to re-write the whole thing regardless, I just need to know where exactly my mistakes are
what about the hydrating issues?
dont you need to fix that first
honestly I still don't even know how hydrating works, I'm trying to wrap my head around how to control re-rendering first, or is hydration the first issue?
yeah it kinda is
it means that the server prerender doesn't match with whats hydrated on the client
@ihyd I've run into the issue of improperly rendering elements and I don't know how to dig myself out of this hole. js const CartItem: React.FC<CartItemProps> = ({ name, image, price, quantity, id, removeFromCart }) => { const [currentQuantity, setQuantity] = useState(quantity); useEffect(() => { setQuantity(quantity); }, [quantity]); const handleQuantityChange = (newQuantity: number) => { setQuantity(newQuantity); updateCartItem(id, newQuantity); }; return ( <span className="flex flex-row p-4 m-4 bg-primary-1000 rounded-lg"> <Image src={image} width={150} height={150} alt="Cart Item Image" /> <span className="p-4 pt-1 flex flex-col justify-center items-center gap-2"> <p className="font-bold top-0 p-2">{name}</p> <p>${price}</p> </span> <span className="flex flex-row justify-center items-center gap-4"> <Quantity quantity={currentQuantity} lowerLimit={1} upperLimit={99} increase={() => handleQuantityChange(currentQuantity + 1)} decrease={() => handleQuantityChange(currentQuantity - 1)} /> <p>Total: {formatCurrency(currentQuantity * price)}</p> </span> <button onClick={() => removeFromCart(id)}>Remove</button> </span> ); }; const YourCart = () => { const [cartItems, setCartItems] = useState<CartItemProps[]>([]); const [loading, setLoading] = useState(true); const [total, setTotal] = useState(0); useEffect(() => { const fetchCartItems = async () => { try { const items = await getCart(); setCartItems(items); setLoading(false); } catch (error) { console.error('Error fetching cart items:', error); setLoading(false); } }; fetchCartItems(); }, []); useEffect(() => { const updateTotal = async () => { try { const cartTotal = await getCartTotal(); setTotal(cartTotal); } catch (error) { console.error('Error updating total:', error); } }; updateTotal(); }, [cartItems]); const handleRemoveFromCart = async (itemId: number) => { try { removeCartItem(itemId); const updatedCartItems = cartItems.filter(item => item.id !== itemId); setCartItems(updatedCartItems); } catch (error) { console.error('Error removing item from cart:', error); } }; return ( <div className="flex flex-row"> <div className="flex flex-col"> <span className="text-lg p-2 text-center mt-4 flex flex-col"> Your cart ({cartItems.length} items) </span> {loading ? ( <Loader /> ) : ( cartItems.map((item) => ( <CartItem key={item.id} name={item.name} image={item.image} price={item.price} quantity={item.quantity} id={item.id} removeFromCart={handleRemoveFromCart} /> )) )} </div> <CheckOut total={total} /> </div> ); }; export default YourCart; This is the full component, I want the total in CheckOut to be changed, and that component re-rendered anytime the quantity changes, or a cart item is added / removed, how can I get this done here? and what am I doing wrong
also you didnt provide where CartItem and YourCart is being used?
It's just being used like this, cart items mapped in YourCart, and YourCart rendered on the page
@aardani it means that the server prerender doesn't match with whats hydrated on the client
i see, that was one of the many issues
I'll try to find a way to restructure it but I don't even know where to start
@ihyd It's just being used like this, cart items mapped in YourCart, and YourCart rendered on the page
So CartPage (in page.js)
returns <div><YourCart/></div>?
yes
it's just supposed to be placeholder until I can make the cart component work as intended
what components is mentioned in the hydration error?
currently none because the cart items work as intended, but i disconnected it from the checkout
my main goal for now is to make it so when the quantity changes in any one of the cart items, the checkout element is re-rendered
and to make the all in-sync for hydration
when a cart item is removed, it properly changes the total but not if quantity is changed
how do you think I should go about structuring this?
firstly you can use Context to distribute your setter and getters
secondly you can just control the "state" of the CartItem in the YourCart component. So move the useState of CartItem one level above and pass down the setter
then I'll focus on hydration and cleaning it up
thank you ^^
yeah so you have 2 option if you dont want to install other library
export YourCart(){
  const [cartItems, setCartItems]  = useState([])
  

  return (
    cartItems.map( (item, idx) => <CartItem onQtyInc={
      () => { 
        setCartItems(prev => {
          prev[idx].qty++
          return [...prev]
        })
      }
    }/>
    
  )
}

export CartItem({
  onQtyInc, 
  onQtyDec,
}){
  return (
    <button onClick={onQtyInc} />
    <button onClick={onQtyDec} />
  )
}
or use context