#Getting value from a cookie in a server action and passing to client components

1 messages · Page 1 of 1 (latest)

azure oxide
#
export async function createCart() {
  try {
    const res = await fetch(`${MEDUSA_BACKEND_URL}/store/carts`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      next: {
        tags: ['cart']
      }
    });
    console.log('Cart creation response', res.body);
    const data = await res.json();
    if (res.status === 200) {
      const cart = data.cart;

      const cookieStore = cookies();

      cookieStore.set('_medusa_cart_id', cart.id, {
        maxAge: 60 * 60 * 24 * 7
      });

      console.log('Cart created', cart);
      revalidateTag('cart');
      return cart as Cart;
    } else {
      throw new Error('Failed to create cart');
    }
  } catch (error) {
    console.log(`Error creating cart: ${error}`);
  }
}
export const getCart = cache(async function (cartId: string) {
  console.log('Cart ID from the cookie', cartId);
  if (!cartId) {
    console.log('No cart ID found');
    return null;
  }
  const expand =
    'customer,shipping_address,billing_address,items.variant.options,items.variant.prices,payment_sessions,shipping_methods';
  const response = await fetch(`${MEDUSA_BACKEND_URL}/store/carts/${cartId}?expand=${expand}`, {
    method: 'GET',
    next: {
      tags: ['cart']
    }
  });
  const data = await response.json();
  if (response.status === 200) {
    const cart = data.cart;
    return cart as Cart;
  } else {
    console.log('Failed to get cart');
    return null;
  }
});
export async function getOrSetCart2() {
  const cartId = cookies().get('_medusa_cart_id')?.value;
  let cart;

  if (cartId) {
    console.log('Getting cart from cookie', cartId);
    cart = await getCart(cartId).then((cart) => cart);
  }

  if (!cart) {
    console.log('Creating new cart');
    cart = await createCart();
    if (cart) {
      revalidateTag('cart');
    }
  }
  return cart as Cart;
}

How to get the cart value in a server component and a client component?

glacial ospreyBOT
#

Hi @azure oxide. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:

#

To get the cart value in a server component, you can use the getOrSetCart2 function you've defined. This function first tries to get the cart ID from a cookie, then fetches the cart with that ID. If no cart is found, it creates a new one. Here's how you can use it in a server component:

import { getOrSetCart2 } from './your-file-path';

export default function ServerComponent() {
  const cart = getOrSetCart2();
  // Use the cart data here
  return '...';
}

For a client component, you can pass the cart data as a prop from the server component. However, keep in mind that the data passed from server to client components needs to be serializable by React. Here's an example:

import { getOrSetCart2 } from './your-file-path';

// Server component
export default function ServerComponent() {
  const cart = getOrSetCart2();
  return <ClientComponent cart={cart} />;
}

// Client component
export const ClientComponent = ({ cart }) => {
  // Use the cart data here
  return '...';
}

Remember that the cookies function allows you to read the HTTP incoming request cookies from a Server Component or write outgoing request cookies in a Server Action or Route Handler. It's a Dynamic Function whose returned values cannot be known ahead of time. Using it in a layout or page will opt a route into dynamic rendering at request time (source (https://nextjs.org/docs/app/api-reference/functions/cookies)).

#

@azure oxide kapa.ai is still learning and improving, please let us know how it did by reacting below

azure oxide
#

@glacial osprey So If I have a header server component that is mouned in a layer, and I want to pass the cart down to a cart popover (client component) where I want to display some info about the cart items, how would I do that?

import { getOrSetCart2 } from '@/actions/actions.cart';
import CartPopover from '@/components/popovers/CartPopover';
import DesktopMenu from '../navigation/DesktopMenu';
import MobileMenu from '../navigation/MobileMenu';

export default async function Header() {
const cart = await getOrSetCart2();
console.log('Cart fetched in Header:', cart);

return (
<header className="sticky top-0 z-50 w-full bg-background shadow-sm">
<div className="container relative mx-auto flex min-h-20 flex-row items-center justify-start gap-4 lg:min-h-10 lg:justify-center">
<div className="hidden w-full lg:block">
<DesktopMenu cart={cart} />
</div>
<div className="flex w-full justify-between bg-background lg:hidden">
<div>
<MobileMenu />
</div>
<div className="flex items-center gap-6">
<CartPopover cart={cart} />
</div>
</div>
</div>
</header>
);
}

Doing this results with:

Error creating cart: Error: Cookies can only be modified in a Server Action or Route Handler. Read more: https://nextjs.org/docs/app/api-reference/functions/cookies#cookiessetname-value-options
Cart fetched in Header: undefined
glacial ospreyBOT
#

Let me check the knowledge base for you. One moment! :books:

#

The error message you're seeing indicates that you're trying to modify cookies outside of a Server Action or Route Handler. In Next.js, cookies can only be set or modified in these contexts. This is because cookies are part of the HTTP protocol and thus need to be handled on the server-side or in a serverless function.

In your case, the getOrSetCart2 function is trying to set a cookie, which is not allowed in a server component like Header.

To resolve this issue, you could move the cart creation logic to a Server Action or Route Handler. Then, you can fetch the cart in your server component using a fetch request to that Server Action or Route Handler.

Here's a simplified example:

// pages/api/cart.js
import { createCart } from '@/actions/actions.cart';

export default async function handler(req, res) {
  if (req.method === 'POST') {
    const cart = await createCart();
    res.status(200).json(cart);
  } else {
    // Handle any other HTTP method
  }
}

Then, in your server component, you can fetch the cart like this:

import CartPopover from '@/components/popovers/CartPopover';
import DesktopMenu from '../navigation/DesktopMenu';
import MobileMenu from '../navigation/MobileMenu';

export default async function Header() {
  const res = await fetch('/api/cart', { method: 'POST' });
  const cart = await res.json();
  console.log('Cart fetched in Header:', cart);
#

return (
<header className="sticky top-0 z-50 w-full bg-background shadow-sm">
<div className="container relative mx-auto flex min-h-20 flex-row items-center justify-start gap-4 lg:min-h-10 lg:justify-center">
<div className="hidden w-full lg:block">
<DesktopMenu cart={cart} />
</div>
<div className="flex w-full justify-between bg-background lg:hidden">
<div>
<MobileMenu />
</div>
<div className="flex items-center gap-6">
<CartPopover cart={cart} />
</div>
</div>
</div>
</header>
);
}


This way, you're creating the cart in a Server Action (which is allowed to modify cookies) and then fetching that cart in your server component.

For more information, you can refer to the **Next.js documentation on cookies** (<https://nextjs.org/docs/app/api-reference/functions/cookies>) and **Server Actions** (<https://nextjs.org/docs/app/api-reference/functions/server-action>).
----------------------
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
----------------------
azure oxide
#

@glacial osprey Since I am using app router, what would the route.ts file look like?

glacial ospreyBOT
#

Let me check the knowledge base for you. One moment! :books:

#

In the context of Next.js App Router, a route.ts file is used to create custom request handlers for a given route using the Web Request and Response APIs. This file allows you to define how your application should respond to different HTTP methods like GET, POST, PUT, PATCH, DELETE, HEAD, and OPTIONS.

Here's an example of what a route.ts file might look like:

export async function GET(request: Request) {
  // Define what should happen when a GET request is made
}

export async function POST(request: Request) {
  // Define what should happen when a POST request is made
}

export async function PUT(request: Request) {
  // Define what should happen when a PUT request is made
}

export async function DELETE(request: Request) {
  // Define what should happen when a DELETE request is made
}

export async function PATCH(request: Request) {
  // Define what should happen when a PATCH request is made
}

// If `OPTIONS` is not defined, Next.js will automatically implement `OPTIONS` and set the appropriate Response `Allow` header depending on the other methods defined in the route handler.
export async function OPTIONS(request: Request) {
  // Define what should happen when an OPTIONS request is made
}

Each function corresponds to an HTTP method and receives a Request object as an argument. You can use this object to access the request data and define how your application should respond to that specific HTTP method.

#

Remember that Route Handlers are only available inside the App Router. You do not need to use API Routes (pages) and Route Handlers (app) together, as Route Handlers should be able to handle all use cases (source (https://nextjs.org/docs/app/api-reference/file-conventions/route)).

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: