docs: added cart storefront guides (#7662)
* docs: added cart storefront guides * add context guides * small fixes to the context
This commit is contained in:
@@ -0,0 +1,180 @@
|
||||
import { CodeTabs, CodeTab } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Create Cart Context in Storefront`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
Throughout your storefront, you'll need to access the customer's cart to perform different actions.
|
||||
|
||||
So, if your storefront is React-based, create a cart context and add it at the top of your components tree. Then, you can access the customer's cart anywhere in your storefront.
|
||||
|
||||
## Create Cart Context Provider
|
||||
|
||||
For example, create the following file that exports a `CartProvider` component and a `useCart` hook:
|
||||
|
||||
export const highlights = [
|
||||
["13", "cart", "Expose cart to children of the context provider."],
|
||||
["14", "setCart", "Allow the context provider's children to update the cart."],
|
||||
["25", "CartProvider", "The provider component to use in your component tree."],
|
||||
["31", "useRegion", "Use the `useRegion` hook defined in the Region Context guide."],
|
||||
["36", "setItem", "Set the cart's ID in `localStorage` in case it changed."],
|
||||
["44", "fetch", "If the customer doesn't have a cart, create a new one."],
|
||||
["48", "process.env.NEXT_PUBLIC_PAK", "Pass the Publishable API key to associate the correct sales channel(s)."],
|
||||
["62", "fetch", "Retrieve the customer's cart."],
|
||||
["82", "useCart", "The hook that child components of the provider use to access the cart."]
|
||||
]
|
||||
|
||||
```tsx highlights={highlights}
|
||||
"use client" // include with Next.js 13+
|
||||
|
||||
import {
|
||||
createContext,
|
||||
useContext,
|
||||
useEffect,
|
||||
useState
|
||||
} from "react"
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import { useRegion } from "./region"
|
||||
|
||||
type CartContextType = {
|
||||
cart?: HttpTypes.StoreCart
|
||||
setCart: React.Dispatch<
|
||||
React.SetStateAction<HttpTypes.StoreCart | undefined>
|
||||
>
|
||||
}
|
||||
|
||||
const CartContext = createContext<CartContextType | null>(null)
|
||||
|
||||
type CartProviderProps = {
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
export const CartProvider = ({ children }: CartProviderProps) => {
|
||||
const [cart, setCart] = useState<
|
||||
HttpTypes.StoreCart
|
||||
>()
|
||||
const { region } = useRegion()
|
||||
|
||||
useEffect(() => {
|
||||
if (cart || !region) {
|
||||
return
|
||||
}
|
||||
|
||||
const cartId = localStorage.getItem("cart_id")
|
||||
if (!cartId) {
|
||||
// create a cart
|
||||
fetch(`http://localhost:9000/store/carts`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"x-publishable-api-key": process.env.NEXT_PUBLIC_PAK || "temp",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
region_id: region.id,
|
||||
})
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then(({ cart: dataCart }) => {
|
||||
localStorage.setItem("cart_id", dataCart.id)
|
||||
setCart(dataCart)
|
||||
})
|
||||
} else {
|
||||
// retrieve cart
|
||||
fetch(`http://localhost:9000/store/carts/${cartId}`, {
|
||||
credentials: "include"
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then(({ cart: dataCart }) => {
|
||||
setCart(dataCart)
|
||||
})
|
||||
}
|
||||
}, [cart, region])
|
||||
|
||||
return (
|
||||
<CartContext.Provider value={{
|
||||
cart,
|
||||
setCart
|
||||
}}>
|
||||
{children}
|
||||
</CartContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export const useCart = () => {
|
||||
const context = useContext(CartContext)
|
||||
|
||||
if (!context) {
|
||||
throw new Error("useCart must be used within a CartProvider")
|
||||
}
|
||||
|
||||
return context
|
||||
}
|
||||
```
|
||||
|
||||
The `CartProvider` handles retrieving or creating the customer's cart. It uses the `useRegion` hook defined in the [Region Context guide](../../regions/context/page.mdx).
|
||||
|
||||
The `useCart` hook returns the value of the `CartContext`. Child components of `CartProvider` use this hook to access `cart` or `setCart`.
|
||||
|
||||
---
|
||||
|
||||
## Use CartProvider in Component Tree
|
||||
|
||||
To use the cart context's value, add the `CartProvider` high in your component tree.
|
||||
|
||||
For example, if you're using Next.js, add it to the `app/layout.tsx` or `src/app/layout.tsx` file:
|
||||
|
||||
```tsx title="app/layout.tsx" collapsibleLines="1-14" highlights={[["23"]]}
|
||||
import type { Metadata } from "next"
|
||||
import { Inter } from "next/font/google"
|
||||
import "./globals.css"
|
||||
import { CartProvider } from "../providers/cart"
|
||||
import { RegionProvider } from "../providers/region"
|
||||
|
||||
const inter = Inter({ subsets: ["latin"] })
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Create Next App",
|
||||
description: "Generated by create next app",
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className={inter.className}>
|
||||
<RegionProvider>
|
||||
<CartProvider>
|
||||
{/* Other providers... */}
|
||||
{children}
|
||||
</CartProvider>
|
||||
</RegionProvider>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Use useCart Hook
|
||||
|
||||
Now, you can use the `useCart` hook in child components of `CartProvider`.
|
||||
|
||||
For example:
|
||||
|
||||
```tsx
|
||||
"use client" // include with Next.js 13+
|
||||
// ...
|
||||
import { useCart } from "../providers/cart"
|
||||
|
||||
export default function Products() {
|
||||
const { cart } = useCart()
|
||||
// ...
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,140 @@
|
||||
import { CodeTabs, CodeTab } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Create Cart in Storefront`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you'll learn how to create and store a cart.
|
||||
|
||||
## Create Cart on First Access
|
||||
|
||||
It's recommended to create a cart the first time a customer accesses a page, then store the cart's ID in the `localStorage`.
|
||||
|
||||
To create a cart, send a request to the [Create Cart API route](!api!/store#carts_postcarts).
|
||||
|
||||
For example:
|
||||
|
||||
<CodeTabs group="store-request">
|
||||
<CodeTab label="Fetch API" value="fetch">
|
||||
|
||||
export const fetchHighlights = [
|
||||
["5", "process.env.NEXT_PUBLIC_PAK", "Pass the Publishable API key to associate the correct sales channel(s)."],
|
||||
["9", "region_id", "Associate the cart with the chosen region for accurate pricing."],
|
||||
["14", "setItem", "Set the cart's ID in the `localStorage`."]
|
||||
]
|
||||
|
||||
```ts highlights={fetchHighlights}
|
||||
fetch(`http://localhost:9000/store/carts`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"x-publishable-api-key": process.env.NEXT_PUBLIC_PAK || "temp",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
region_id: region.id,
|
||||
})
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then(({ cart }) => {
|
||||
localStorage.setItem("cart_id", cart.id)
|
||||
})
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="React" value="react">
|
||||
|
||||
export const highlights = [
|
||||
["8", "region", "Assuming you previously retrieved the chosen region."],
|
||||
["15", "cartId", "Retrieve the cart ID from `localStorage`, if exists."],
|
||||
["21", "", "Send a request to create the cart."],
|
||||
["26", "process.env.NEXT_PUBLIC_PAK", "Pass the Publishable API key to associate the correct sales channel(s)."],
|
||||
["30", "region_id", "Associate the cart with the chosen region for accurate pricing."],
|
||||
["35", "setItem", "Set the cart's ID in the `localStorage`."]
|
||||
]
|
||||
|
||||
```tsx highlights={highlights}
|
||||
"use client" // include with Next.js 13+
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
// other imports...
|
||||
|
||||
export default function Home() {
|
||||
// TODO assuming you have the region retrieved
|
||||
const region = {
|
||||
// ...
|
||||
}
|
||||
|
||||
// ...
|
||||
|
||||
useEffect(() => {
|
||||
const cartId = localStorage.getItem("cart_id")
|
||||
if (cartId) {
|
||||
// customer already has a cart created
|
||||
return
|
||||
}
|
||||
|
||||
// create a cart and store it in the localStorage
|
||||
fetch(`http://localhost:9000/store/carts`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"x-publishable-api-key": process.env.NEXT_PUBLIC_PAK || "temp",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
region_id: region.id,
|
||||
})
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then(({ cart }) => {
|
||||
localStorage.setItem("cart_id", cart.id)
|
||||
})
|
||||
}, [])
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
{/* TODO add a link to cart object in API reference (once available). */}
|
||||
|
||||
The response of the Create Cart API route has a `cart` field, which is a cart object.
|
||||
|
||||
Refer to the [Create Cart API reference](!api!/store#carts_postcarts) for details on other available request parameters.
|
||||
|
||||
### Publishable API Key
|
||||
|
||||
When you create a cart, you pass the publishable API key in the header of the request. This associates the cart with the sales channel(s) of the publishable API key.
|
||||
|
||||
This is necessary, as only products matching the cart's sales channel(s) can be added to the cart.
|
||||
|
||||
---
|
||||
|
||||
## Associate Customer with Cart
|
||||
|
||||
When creating the cart, you can associate the logged-in customer's ID with the cart by passing a `customer_id` request body parameter.
|
||||
|
||||
For example:
|
||||
|
||||
export const customerHighlights = [
|
||||
["5", "customer.id", "Assuming you have the customer object."]
|
||||
]
|
||||
|
||||
```ts highlights={customerHighlights}
|
||||
fetch(`http://localhost:9000/store/carts`, {
|
||||
// ...
|
||||
body: JSON.stringify({
|
||||
// ...
|
||||
customer_id: customer.id
|
||||
})
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then(({ cart }) => {
|
||||
localStorage.setItem("cart_id", cart.id)
|
||||
})
|
||||
```
|
||||
@@ -0,0 +1,150 @@
|
||||
import { CodeTabs, CodeTab } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Manage Cart's Items in Storefront`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you'll learn how to manage a cart's line items, including adding, updating, and removing them.
|
||||
|
||||
## Add Product Variant to Cart
|
||||
|
||||
{/* TODO add section on checking variant quantity once it's fixed in v2. */}
|
||||
|
||||
To add a product variant to a cart, use the [Add Line Item API route](!api!/store#carts_postcartsidlineitems).
|
||||
|
||||
For example:
|
||||
|
||||
export const addHighlights = [
|
||||
["1", "variant_id", "The ID of the selected variant."],
|
||||
["2", "cartId", "Retrieve the cart ID from the `localStorage`."],
|
||||
["16", "quantity", "You can also allow customers to specify the quantity."]
|
||||
]
|
||||
|
||||
```ts highlights={addHighlights}
|
||||
const addToCart = (variant_id: string) => {
|
||||
const cartId = localStorage.getItem("cart_id")
|
||||
|
||||
if (!cartId) {
|
||||
return
|
||||
}
|
||||
|
||||
fetch(`http://localhost:9000/store/carts/${cartId}/line-items`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
variant_id,
|
||||
quantity: 1,
|
||||
})
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then(({ cart }) => {
|
||||
// use cart
|
||||
console.log(cart)
|
||||
alert("Product added to cart")
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
The Add Line Item API route requires two request body parameters:
|
||||
|
||||
- `variant_id`: The ID of the product variant to add to the cart. This is the variant selected by the customer.
|
||||
- `quantity`: The quantity to add to cart.
|
||||
|
||||
The API route returns the updated cart object.
|
||||
|
||||
---
|
||||
|
||||
## Update Line Item in Cart
|
||||
|
||||
You can update the quantity of a line item in the cart using the [Update Line Item API route](!api!/store#carts_postcartsidlineitemsline_id).
|
||||
|
||||
For example:
|
||||
|
||||
export const updateHighlights = [
|
||||
["2", "itemId", "The ID of the item to update."],
|
||||
["3", "quantity", "The new quantity of the item."],
|
||||
["5", "cartId", "Retrieve the cart ID from the `localStorage`."],
|
||||
["12", "itemId", "Pass the item's ID as a path parameter."],
|
||||
]
|
||||
|
||||
```ts highlights={updateHighlights}
|
||||
const updateQuantity = (
|
||||
itemId: string,
|
||||
quantity: number
|
||||
) => {
|
||||
const cartId = localStorage.getItem("cart_id")
|
||||
|
||||
if (!cartId) {
|
||||
return
|
||||
}
|
||||
|
||||
fetch(`http://localhost:9000/store/carts/${cartId}/line-items/${
|
||||
itemId
|
||||
}`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
quantity
|
||||
})
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then(({ cart }) => {
|
||||
// use cart
|
||||
console.log(cart)
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
The Update Line Item API route requires:
|
||||
|
||||
- The line item's ID to be passed as a path parameter.
|
||||
- The `quantity` request body parameter, which is the new quantity of the item.
|
||||
|
||||
The API route returns the updated cart object.
|
||||
|
||||
---
|
||||
|
||||
## Remove Line Item from Cart
|
||||
|
||||
To remove a line item from the cart, send a request to the [Remove Line Item API route](!api!/store#carts_deletecartsidlineitemsline_id).
|
||||
|
||||
For example:
|
||||
|
||||
export const deleteHighlights = [
|
||||
["1", "itemId", "The ID of the line item to remove."],
|
||||
["2", "cartId", "Retrieve the cart ID from the `localStorage`."],
|
||||
["9", "itemId", "Pass the item's ID as a path parameter."],
|
||||
["15", "parent", "The updated cart is returned as the `parent` field."]
|
||||
]
|
||||
|
||||
```ts highlights={deleteHighlights}
|
||||
const removeItem = (itemId: string) => {
|
||||
const cartId = localStorage.getItem("cart_id")
|
||||
|
||||
if (!cartId) {
|
||||
return
|
||||
}
|
||||
|
||||
fetch(`http://localhost:9000/store/carts/${cartId}/line-items/${
|
||||
itemId
|
||||
}`, {
|
||||
credentials: "include",
|
||||
method: "DELETE",
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then(({ parent: cart }) => {
|
||||
// use cart
|
||||
console.log(cart)
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
The Delete Line Item API route returns the updated cart object as the `parent` field.
|
||||
@@ -0,0 +1,13 @@
|
||||
import { ChildDocs } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Carts in Storefront`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
A cart holds the items that the customer wants to purchase.
|
||||
|
||||
Using Medusa's Store APIs, you can create a cart for the customer and allow them to add, update, and remove items from the cart.
|
||||
|
||||
<ChildDocs type="item" onlyTopLevel={true} />
|
||||
@@ -0,0 +1,129 @@
|
||||
import { CodeTabs, CodeTab } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Retrieve Cart in Storefront`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
You can retrieve a cart by sending a request to the [Get a Cart API route](!api!/store#carts_getcartsid).
|
||||
|
||||
Assuming you stored the cart's ID in the `localStorage` as explained in the [Create Cart guide](../create/page.mdx), pass that ID as a path parameter to the request.
|
||||
|
||||
For example:
|
||||
|
||||
<CodeTabs group="store-request">
|
||||
<CodeTab label="Fetch API" value="fetch">
|
||||
|
||||
export const fetchHighlights = [
|
||||
["1", "cartId", "Pass the customer's cart ID as a path parameter."],
|
||||
]
|
||||
|
||||
```ts highlights={fetchHighlights}
|
||||
fetch(`http://localhost:9000/store/carts/${cartId}`, {
|
||||
credentials: "include"
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then(({ cart }) => {
|
||||
// use cart...
|
||||
console.log(cart)
|
||||
})
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="React" value="react">
|
||||
|
||||
export const highlights = [
|
||||
["16", "cartId", "Retrieve the cart ID from `localStorage`."],
|
||||
["18", "TODO", "You can create the cart and set it here as explained in the Create Cart guide."],
|
||||
["22"], ["23"], ["24"], ["25"], ["26"], ["27"], ["28"],
|
||||
["31", "formatPrice", "This function was previously created to format product prices. You can re-use the same function."],
|
||||
["34", "currency_code", "If you reuse the `formatPrice` function, pass the currency code as a parameter."],
|
||||
]
|
||||
|
||||
```tsx highlights={highlights}
|
||||
"use client" // include with Next.js 13+
|
||||
|
||||
import { useEffect, useState } from "react"
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
|
||||
export default function Cart () {
|
||||
const [cart, setCart] = useState<
|
||||
HttpTypes.StoreCart
|
||||
>()
|
||||
|
||||
useEffect(() => {
|
||||
if (cart) {
|
||||
return
|
||||
}
|
||||
|
||||
const cartId = localStorage.getItem("cart_id")
|
||||
if (!cartId) {
|
||||
// TODO create cart
|
||||
return
|
||||
}
|
||||
|
||||
fetch(`http://localhost:9000/store/carts/${cartId}`, {
|
||||
credentials: "include"
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then(({ cart: dataCart }) => {
|
||||
setCart(dataCart)
|
||||
})
|
||||
}, [cart])
|
||||
|
||||
const formatPrice = (amount: number): string => {
|
||||
return new Intl.NumberFormat("en-US", {
|
||||
style: "currency",
|
||||
currency: cart?.currency_code,
|
||||
})
|
||||
.format(amount)
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{!cart && <span>Loading...</span>}
|
||||
{cart && (
|
||||
<>
|
||||
<span>Cart ID: {cart.id}</span>
|
||||
<ul>
|
||||
{cart.items?.map((item) => (
|
||||
<li key={item.id}>
|
||||
{item.title} -
|
||||
Quantity: {item.quantity} -
|
||||
Price: {formatPrice(item.unit_price)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<span>Cart Total: {formatPrice(cart.total)}</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
{/* TODO add a link to cart object in API reference (once available). */}
|
||||
|
||||
The response of the Retrieve Cart API route has a `cart` field, which is a cart object.
|
||||
|
||||
---
|
||||
|
||||
## Format Prices
|
||||
|
||||
When displaying the cart's totals or line item's price, make sure to format the price as implemented in the `formatPrice` function shown in the above snippet:
|
||||
|
||||
```ts
|
||||
const formatPrice = (amount: number): string => {
|
||||
return new Intl.NumberFormat("en-US", {
|
||||
style: "currency",
|
||||
currency: cart?.currency_code,
|
||||
})
|
||||
.format(amount)
|
||||
}
|
||||
```
|
||||
|
||||
Since this is the same function used to format the prices of products, you can define the function in one place and re-use it where necessary. In that case, make sure to pass the currency code as a parameter.
|
||||
@@ -0,0 +1,77 @@
|
||||
import { CodeTabs, CodeTab } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Update Cart in Storefront`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you'll learn how to update different details of a cart.
|
||||
|
||||
<Note>
|
||||
|
||||
All cart updates are performed using the [Update Cart API route](!api!/store#carts_postcartsid).
|
||||
|
||||
</Note>
|
||||
|
||||
## Update Cart's Region
|
||||
|
||||
If a customer changes their region, you must update their cart to be associated with that region.
|
||||
|
||||
For example:
|
||||
|
||||
export const updateRegionHighlights = [
|
||||
["8", `"new_id"`, "Pass the new chosen region's ID."]
|
||||
]
|
||||
|
||||
```ts highlights={updateRegionHighlights}
|
||||
fetch(`http://localhost:9000/store/carts/${cartId}`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
region_id: "new_id"
|
||||
})
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then(({ cart }) => {
|
||||
// use cart...
|
||||
console.log(cart)
|
||||
})
|
||||
```
|
||||
|
||||
The Update Cart API route accepts a `region_id` request body parameter, whose value is the new region to associate with the cart.
|
||||
|
||||
---
|
||||
|
||||
## Update Cart's Customer
|
||||
|
||||
If a guest customer logs in, you must update the cart to be associated with the logged-in customer.
|
||||
|
||||
For example:
|
||||
|
||||
export const updateCustomerHighlights = [
|
||||
["8", `"logged_in_id"`, "Pass the logged-in customer's ID."]
|
||||
]
|
||||
|
||||
```ts highlights={updateCustomerHighlights}
|
||||
fetch(`http://localhost:9000/store/carts/${cartId}`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
customer_id: "logged_in_id"
|
||||
})
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then(({ cart }) => {
|
||||
// use cart...
|
||||
console.log(cart)
|
||||
})
|
||||
```
|
||||
|
||||
The Update Cart API route accepts a `customer_id` request body parameter, whose value is the customer to associate with the cart.
|
||||
@@ -0,0 +1,171 @@
|
||||
import { CodeTabs, CodeTab } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Region Context in Storefront`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
Throughout your storefront, you'll need to access the selected region to perform different actions, such as retrieve product's prices in the selected region.
|
||||
|
||||
So, if your storefront is React-based, create a region context and add it at the top of your components tree. Then, you can access the selected region anywhere in your storefront.
|
||||
|
||||
## Create Region Context Provider
|
||||
|
||||
For example, create the following file that exports a `RegionProvider` component and a `useRegion` hook:
|
||||
|
||||
export const highlights = [
|
||||
["12", "region", "Expose region to children of the context provider."],
|
||||
["13", "setRegion", "Allow the context provider's children to change the selected region."],
|
||||
["24", "RegionProvider", "The provider component to use in your component tree."],
|
||||
["32", "", "If a region is set, set its ID in the local storage again in case it changed."],
|
||||
["39", "regionId", "Retrieve the selected region from the `localStorage`."],
|
||||
["42", "fetch", "If no region is selected, retrieve the list of regions from the Medusa application and select the first one."],
|
||||
["51", "fetch", "If a region is selected, retrieve it from the Medusa application."],
|
||||
["71", "useRegion", "The hook that child components of the provider use to access the region."]
|
||||
]
|
||||
|
||||
```tsx highlights={highlights}
|
||||
"use client" // include with Next.js 13+
|
||||
|
||||
import {
|
||||
createContext,
|
||||
useContext,
|
||||
useEffect,
|
||||
useState
|
||||
} from "react"
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
|
||||
type RegionContextType = {
|
||||
region?: HttpTypes.StoreRegion
|
||||
setRegion: React.Dispatch<
|
||||
React.SetStateAction<HttpTypes.StoreRegion | undefined>
|
||||
>
|
||||
}
|
||||
|
||||
const RegionContext = createContext<RegionContextType | null>(null)
|
||||
|
||||
type RegionProviderProps = {
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
export const RegionProvider = (
|
||||
{ children }: RegionProviderProps
|
||||
) => {
|
||||
const [region, setRegion] = useState<
|
||||
HttpTypes.StoreRegion
|
||||
>()
|
||||
|
||||
useEffect(() => {
|
||||
if (region) {
|
||||
// set its ID in the local storage in
|
||||
// case it changed
|
||||
localStorage.setItem("region_id", region.id)
|
||||
return
|
||||
}
|
||||
|
||||
const regionId = localStorage.getItem("region_id")
|
||||
if (!regionId) {
|
||||
// retrieve regions and select the first one
|
||||
fetch(`http://localhost:9000/store/regions`, {
|
||||
credentials: "include"
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then(({ regions }) => {
|
||||
setRegion(regions[0])
|
||||
})
|
||||
} else {
|
||||
// retrieve selected region
|
||||
fetch(`http://localhost:9000/store/regions/${regionId}`, {
|
||||
credentials: "include"
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then(({ region: dataRegion }) => {
|
||||
setRegion(dataRegion)
|
||||
})
|
||||
}
|
||||
}, [region])
|
||||
|
||||
return (
|
||||
<RegionContext.Provider value={{
|
||||
region,
|
||||
setRegion
|
||||
}}>
|
||||
{children}
|
||||
</RegionContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export const useRegion = () => {
|
||||
const context = useContext(RegionContext)
|
||||
|
||||
if (!context) {
|
||||
throw new Error("useRegion must be used within a RegionProvider")
|
||||
}
|
||||
|
||||
return context
|
||||
}
|
||||
```
|
||||
|
||||
The `RegionProvider` handles retrieving the selected region from the Medusa application, and updating its ID in the `localStorage`.
|
||||
|
||||
The `useRegion` hook returns the value of the `RegionContext`. Child components of `RegionProvider` use this hook to access `region` or `setRegion`.
|
||||
|
||||
---
|
||||
|
||||
## Use RegionProvider in Component Tree
|
||||
|
||||
To use the region context's value, add the `RegionProvider` high in your component tree.
|
||||
|
||||
For example, if you're using Next.js, add it to the `app/layout.tsx` or `src/app/layout.tsx` file:
|
||||
|
||||
```tsx title="app/layout.tsx" collapsibleLines="1-14" highlights={[["22"]]}
|
||||
import type { Metadata } from "next"
|
||||
import { Inter } from "next/font/google"
|
||||
import "./globals.css"
|
||||
import { CartProvider } from "../providers/cart"
|
||||
import { RegionProvider } from "../providers/region"
|
||||
|
||||
const inter = Inter({ subsets: ["latin"] });
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Create Next App",
|
||||
description: "Generated by create next app",
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className={inter.className}>
|
||||
<RegionProvider>
|
||||
{/* Other providers... */}
|
||||
{children}
|
||||
</RegionProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Use useRegion Hook
|
||||
|
||||
Now, you can use the `useRegion` hook in child components of `RegionProvider`.
|
||||
|
||||
For example:
|
||||
|
||||
```tsx
|
||||
"use client" // include with Next.js 13+
|
||||
// ...
|
||||
import { useRegion } from "../providers/region"
|
||||
|
||||
export default function Products() {
|
||||
const { region } = useRegion()
|
||||
// ...
|
||||
}
|
||||
```
|
||||
+1
-1
@@ -8,7 +8,7 @@ In this document, you'll learn how to store a customer's region's ID and retriev
|
||||
|
||||
## Store Selected Region ID
|
||||
|
||||
When the customer selects their region, for example, from a dropdown, store that region's ID in the `localstorage`.
|
||||
When the customer selects their region, for example, from a dropdown, store that region's ID in the `localStorage`.
|
||||
|
||||
For example:
|
||||
|
||||
|
||||
@@ -871,10 +871,38 @@ export const filesMap = [
|
||||
"filePath": "/www/apps/resources/app/references/[...slug]/page.tsx",
|
||||
"pathname": "/references/[...slug]"
|
||||
},
|
||||
{
|
||||
"filePath": "/www/apps/resources/app/storefront-development/cart/context/page.mdx",
|
||||
"pathname": "/storefront-development/cart/context"
|
||||
},
|
||||
{
|
||||
"filePath": "/www/apps/resources/app/storefront-development/cart/create/page.mdx",
|
||||
"pathname": "/storefront-development/cart/create"
|
||||
},
|
||||
{
|
||||
"filePath": "/www/apps/resources/app/storefront-development/cart/manage-items/page.mdx",
|
||||
"pathname": "/storefront-development/cart/manage-items"
|
||||
},
|
||||
{
|
||||
"filePath": "/www/apps/resources/app/storefront-development/cart/page.mdx",
|
||||
"pathname": "/storefront-development/cart"
|
||||
},
|
||||
{
|
||||
"filePath": "/www/apps/resources/app/storefront-development/cart/retrieve/page.mdx",
|
||||
"pathname": "/storefront-development/cart/retrieve"
|
||||
},
|
||||
{
|
||||
"filePath": "/www/apps/resources/app/storefront-development/cart/update/page.mdx",
|
||||
"pathname": "/storefront-development/cart/update"
|
||||
},
|
||||
{
|
||||
"filePath": "/www/apps/resources/app/storefront-development/page.mdx",
|
||||
"pathname": "/storefront-development"
|
||||
},
|
||||
{
|
||||
"filePath": "/www/apps/resources/app/storefront-development/regions/context/page.mdx",
|
||||
"pathname": "/storefront-development/regions/context"
|
||||
},
|
||||
{
|
||||
"filePath": "/www/apps/resources/app/storefront-development/regions/list/page.mdx",
|
||||
"pathname": "/storefront-development/regions/list"
|
||||
|
||||
@@ -7130,6 +7130,56 @@ export const generatedSidebar = [
|
||||
"path": "/storefront-development/regions/store-retrieve-region",
|
||||
"title": "Store and Retrieve Regions",
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"loaded": true,
|
||||
"isPathHref": true,
|
||||
"path": "/storefront-development/regions/context",
|
||||
"title": "Region React Context",
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"loaded": true,
|
||||
"isPathHref": true,
|
||||
"path": "/storefront-development/cart",
|
||||
"title": "Carts",
|
||||
"children": [
|
||||
{
|
||||
"loaded": true,
|
||||
"isPathHref": true,
|
||||
"path": "/storefront-development/cart/create",
|
||||
"title": "Create Cart",
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"loaded": true,
|
||||
"isPathHref": true,
|
||||
"path": "/storefront-development/cart/retrieve",
|
||||
"title": "Retrieve Cart",
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"loaded": true,
|
||||
"isPathHref": true,
|
||||
"path": "/storefront-development/cart/context",
|
||||
"title": "Cart React Context",
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"loaded": true,
|
||||
"isPathHref": true,
|
||||
"path": "/storefront-development/cart/update",
|
||||
"title": "Update Cart",
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"loaded": true,
|
||||
"isPathHref": true,
|
||||
"path": "/storefront-development/cart/manage-items",
|
||||
"title": "Manage Line Items",
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1803,6 +1803,36 @@ export const sidebar = sidebarAttachHrefCommonOptions([
|
||||
path: "/storefront-development/regions/store-retrieve-region",
|
||||
title: "Store and Retrieve Regions",
|
||||
},
|
||||
{
|
||||
path: "/storefront-development/regions/context",
|
||||
title: "Region React Context",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/storefront-development/cart",
|
||||
title: "Carts",
|
||||
children: [
|
||||
{
|
||||
path: "/storefront-development/cart/create",
|
||||
title: "Create Cart",
|
||||
},
|
||||
{
|
||||
path: "/storefront-development/cart/retrieve",
|
||||
title: "Retrieve Cart",
|
||||
},
|
||||
{
|
||||
path: "/storefront-development/cart/context",
|
||||
title: "Cart React Context",
|
||||
},
|
||||
{
|
||||
path: "/storefront-development/cart/update",
|
||||
title: "Update Cart",
|
||||
},
|
||||
{
|
||||
path: "/storefront-development/cart/manage-items",
|
||||
title: "Manage Line Items",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user