How do I properly re-render an updated shoppingcart/ checkout in next.js?

126 Views Asked by At

Im building an ecommerce, and am using Saleor as my headless backend. For my storefront, I initiated a project from a Next.js storefront starter for saleor.

Now I have run into an issue, and since Im fairly new to React/ Next.js, and FE in general, I have a question regarding state management of the shopping cart.

The Checkout(cart) is stored on the server, and this is pre configured by saleor. In the storefront app, the Checkout ID is stored in a cookie, which is then fetched to render the cart. The method they used setting this up requires all the cart actions, i.e adding item, removing etc, to call router.refresh(); in order for the cart to re-render the new updated state. IMO, this is not desired, as it makes CSS and etc reload, causing wierd flickering etc on the screen. I would like to have the cart state updated without having to do a page refresh. How can I accomplish this?

cart.tsx

import { getCart } from 'lib/saleor';
import { cookies } from 'next/headers';
import CartModal from './modal';

export default async function Cart() {
  const cartId = cookies().get('cartId')?.value;
  let cart;

  if (cartId) {
    cart = await getCart(cartId);
  }

  return <CartModal cart={cart} />;
}

add-to-cart.tsx

'use client';

import { PlusIcon } from '@heroicons/react/24/outline';
import clsx from 'clsx';
import { addItem } from 'components/cart/actions';
import LoadingDots from 'components/loading-dots';
import { ProductVariant } from 'lib/types';
import { useRouter, useSearchParams } from 'next/navigation';
import { useTransition } from 'react';

export function AddToCart({
  variants,
  availableForSale,
}: {
  variants: ProductVariant[];
  availableForSale: boolean;
}) {
  const router = useRouter();
  const searchParams = useSearchParams();
  const [isPending, startTransition] = useTransition();
  const defaultVariantId = variants.length === 1 ? variants[0]?.id : undefined;
  const variant = variants.find((variant: ProductVariant) =>
    variant.selectedOptions.every(
      (option) => option.value === searchParams.get(option.name.toLowerCase()),
    ),
  );
  const selectedVariantId = variant?.id || defaultVariantId;
  const title = !availableForSale
    ? 'Out of stock'
    : !selectedVariantId
    ? 'Please select options'
    : undefined;

  return (
    <button
      aria-label="Add item to cart"
      disabled={isPending || !availableForSale || !selectedVariantId}
      title={title}
      onClick={() => {
        // Safeguard in case someone messes with `disabled` in devtools.
        if (!availableForSale || !selectedVariantId) return;

        startTransition(async () => {
          const error = await addItem(selectedVariantId);

          if (error) {
            // Trigger the error boundary in the root error.js
            throw new Error(error.toString());
          }

          router.refresh();
        });
      }}
      className={clsx(
        'relative flex w-full items-center justify-center rounded-full bg-blue-600 p-4 tracking-wide text-white hover:opacity-90',
        {
          'cursor-not-allowed opacity-60 hover:opacity-60': !availableForSale || !selectedVariantId,
          'cursor-not-allowed': isPending,
        },
      )}
    >
      <div className="absolute left-0 ml-4">
        {!isPending ? <PlusIcon className="h-5" /> : <LoadingDots className="mb-3 bg-white" />}
      </div>
      <span>{availableForSale ? 'Add To Cart' : 'Out Of Stock'}</span>
    </button>
  );
}

I have tried some different solutions, but since I'm fairly unexperienced I would like some guidance and make sure i'm approaching this in the right way.

0

There are 0 best solutions below