Medusa React
Medusa React is a React library that provides a set of utilities and hooks for interacting seamlessly with the Medusa backend.
For example, if you're creating a storefront with frameworks like Nuxt, you can send requests to the backend using this client. You can also use it in your Medusa Admin customizations.
This reference provides details on the available hooks, providers, and utilities, including examples of each.
Installation
In the directory holding your React-based storefront or admin dashboard, run the following command to install Medusa React:
In addition to the medusa-react
library, you need the following libraries:
@tanstack/react-query
:medusa-react
is built on top of Tanstack Query v4.22. You’ll learn later in this reference how you can use Mutations and Queries with Medusa React.@medusajs/medusa
: The core Medusa package. This is used to import types used by Medusa React and while developing with it.
Part of the Medusa roadmap is to move the types into a separate package, removing the need to install the core Medusa package in your storefront or admin dashboard. You can check other items on our roadmap in GitHub Discussions.
Usage
To use the hooks exposed by Medusa React, include the MedusaProvider
somewhere up in your component tree.
The MedusaProvider
requires two props:
baseUrl
: The URL to your Medusa backendqueryClientProviderProps
: An object used to set the Tanstack Query client. The object requires aclient
property, which should be an instance of QueryClient.
Learn about other optional props in this reference
For example:
import { MedusaProvider } from "medusa-react"
import Storefront from "./Storefront"
import { QueryClient } from "@tanstack/react-query"
import React from "react"
const queryClient = new QueryClient()
const App = () => {
return (
<MedusaProvider
queryClientProviderProps={{ client: queryClient }}
baseUrl="http://localhost:9000"
>
<Storefront />
</MedusaProvider>
)
}
export default App
In the example above, you wrap the Storefront
component with the MedusaProvider
. Storefront
is assumed to be the top-level component of your storefront, but you can place MedusaProvider
at any point in your tree.
Only children of MedusaProvider
can benefit from its hooks. So, the Storefront
component and its child components can now use hooks exposed by Medusa React.
Troubleshooting: Could not find a declaration file for module 'medusa-react'
How to Use this Reference
You'll find in the sidebar three main categories to explore:
- Hooks: Includes all hooks used to send requests to the backend. Hooks are also split into Admin hooks that send requests to the admin, and Store hooks, that send requests to the store.
- Providers: Includes React providers helpful for your development using Medusa React.
- Utilities: Utility functions that are mainly useful for displaying product and variant pricing.
Queries and Mutations
Since Medusa React is built on top of Tanstack Queries, hooks can either be queries or mutations.
Queries
To fetch data from the Medusa backend (in other words, perform GET
requests), you can use Queries.
Query hooks simply wrap around Tanstack Query's useQuery
hook to fetch data from your Medusa backend.
For example, to fetch products from your Medusa backend:
import { Product } from "@medusajs/medusa"
import { useProducts } from "medusa-react"
const Products = () => {
const { products, isLoading } = useProducts()
return isLoading ? (
<div>
Loading...
</div>
) : (
<ul>
{products?.map((product: Product) => (
<li key={product.id}>
{product.title}
</li>
))}
</ul>
)
}
export default Products
In the example above, you import the useProducts
hook from medusa-react
.
This hook, and every other query hook exposed by medusa-react
, returns everything that useQuery
returns in Tanstack Query, except for the data
field. Instead of the data
field, the response data is flattened and is part of the hooks’ returned fields (see products
in the example above).
You can learn more about using queries in Tanstack Query’s documentation.
Mutations
To create, update, or delete data on the Medusa backend (in other words, perform POST
, PUT
, and DELETE
requests), you can use Mutations.
Mutation hooks wrap around Tanstack Query's useMutation
to mutate data on your Medusa backend.
For example, to create a cart:
import { useCreateCart } from "medusa-react"
const Cart = () => {
const createCart = useCreateCart()
const handleClick = () => {
createCart.mutate({}) // create an empty cart
}
return (
<div>
{createCart.isLoading && <div>Loading...</div>}
{!createCart.data?.cart && (
<button onClick={handleClick}>
Create cart
</button>
)}
{createCart.data?.cart?.id && (
<div>Cart ID: {createCart.data?.cart.id}</div>
)}
</div>
)
}
export default Cart
In the example above, you import the useCreateCart
hook from medusa-react
. This hook, and every other mutation hook exposed by medusa-react
, returns everything that useMutation returns. You can also pass the same options you would pass to useMutation
to mutation hooks exposed by medusa-react
.
To create a cart, you call the createCart.mutate
method. In the underlying logic, this method sends a POST
request to the Medusa backend to create a cart.
If the request accepts any parameters, they can be passed as parameters to the mutate
request. For example:
Instead of using mutate
, you can use mutateAsync
to receive a Promise that resolves on success or throws on error.
Learn more about how you can use mutations in Tanstack Query’s documentation.
Authentication
Admin Authentication
There are two ways to authenticate an admin user:
- Using the useAdminLogin hook. This hook tries to authenticate the user by their email and password credential and, if successful, attaches the cookie session ID to subsequent requests.
- Using the
apiKey
option of the MedusaProvider if the admin has an API key. If the admin doesn't have an API key, you can create one using the useAdminUpdateUser hook or the Update User API route.
For example:
import React from "react"
import { useAdminLogin } from "medusa-react"
const Login = () => {
const adminLogin = useAdminLogin()
// ...
const handleLogin = () => {
adminLogin.mutate({
email: "user@example.com",
password: "supersecret",
}, {
onSuccess: ({ user }) => {
console.log(user)
// send authenticated requests now
}
})
}
// ...
}
export default Login
import { MedusaProvider } from "medusa-react"
import Storefront from "./Storefront"
import { QueryClient } from "@tanstack/react-query"
import React from "react"
const queryClient = new QueryClient()
const App = () => {
return (
<MedusaProvider
queryClientProviderProps={{ client: queryClient }}
baseUrl="http://localhost:9000"
apiKey={process.env.API_KEY}
>
<Storefront />
</MedusaProvider>
)
}
export default App
Customer Authentication
To authenticate a customer, use the useMedusa hook to access the underlying Medusa JS Client instance and use one of its authentication methods, such as the authenticate method.
For example:
import React from "react"
import { useMeCustomer, useMedusa } from "medusa-react"
const CustomerLogin = () => {
const { client } = useMedusa()
const { refetch: refetchCustomer } = useMeCustomer()
// ...
const handleLogin = (
email: string,
password: string
) => {
client.auth.authenticate({
email,
password,
})
.then(() => {
// customer is logged-in successfully
// send authenticated requests now
refetchCustomer()
})
.catch(() => {
// an error occurred.
})
}
// ...
}
The refetch method is available through Tanstack Query's useQuery hook. It allows you to refetch data if a change occurs. In this case, you refetch the logged-in customer after authentication.
Publishable API Key
Publishable API Keys allow you to send a request to Store API routes with a pre-defined scope. You can associate the publishable API key with one or more resources, such as sales channels, then include the publishable API key in the header of your requests.
The Medusa backend will infer the scope of the current request based on the publishable API key. At the moment, publishable API keys only work with sales channels.
It's highly recommended to create a publishable API key and pass it as an initialization option of the Medusa client.
You can learn more about publishable API keys and how to use them in this documentation.
Create a Publishable API Key
You can create a publishable API key either using the admin REST APIs, or using the Medusa Admin dashboard.
Use a Publishable API Key
To use the publishable API key, pass it as a prop to the MedusaProvider.
For example:
import { MedusaProvider } from "medusa-react"
import Storefront from "./Storefront"
import { QueryClient } from "@tanstack/react-query"
import React from "react"
const queryClient = new QueryClient()
const App = () => {
return (
<MedusaProvider
queryClientProviderProps={{ client: queryClient }}
baseUrl="http://localhost:9000"
publishableApiKey={process.env.PUB_API_KEY}
>
<Storefront />
</MedusaProvider>
)
}
export default App
HTTP Compression
If you've enabled HTTP Compression in your Medusa backend configurations, and you want to disable it for your requests with Medusa React, you can pass the x-no-compression
header in the customHeaders
prop of the MedusaProvider.
For example:
import { MedusaProvider } from "medusa-react"
import Storefront from "./Storefront"
import { QueryClient } from "@tanstack/react-query"
import React from "react"
const queryClient = new QueryClient()
const App = () => {
return (
<MedusaProvider
queryClientProviderProps={{ client: queryClient }}
baseUrl="http://localhost:9000"
customHeaders={{
"x-no-compression": true,
}}
>
<Storefront />
</MedusaProvider>
)
}
export default App
Expanding Fields
In many hooks you'll find an expand
property that can be accepted within one of the hooks's parameters. You can use the expand
property to unpack an entity's relations and return them in the response.
The relations you pass to expand
replace any relations that are expanded by default in the request.
Expanding One Relation
For example, when you retrieve products, you can retrieve their collection by passing to the expand
query parameter the value collection
:
import React from "react"
import { useAdminProducts } from "medusa-react"
const Products = () => {
const { products, isLoading } = useAdminProducts({
expand: "collection",
})
return (
<div>
{isLoading && <span>Loading...</span>}
{products && !products.length && <span>No Products</span>}
{products && products.length > 0 && (
<ul>
{products.map((product) => (
<li key={product.id}>{product.title}</li>
))}
</ul>
)}
</div>
)
}
export default Products
Expanding Multiple Relations
You can expand more than one relation by separating the relations in the expand
query parameter with a comma.
For example, to retrieve both the variants and the collection of products, pass to the expand
query parameter the value variants,collection
:
import React from "react"
import { useAdminProducts } from "medusa-react"
const Products = () => {
const { products, isLoading } = useAdminProducts({
expand: "variants,collection",
})
return (
<div>
{isLoading && <span>Loading...</span>}
{products && !products.length && <span>No Products</span>}
{products && products.length > 0 && (
<ul>
{products.map((product) => (
<li key={product.id}>{product.title}</li>
))}
</ul>
)}
</div>
)
}
export default Products
Prevent Expanding Relations
Some requests expand relations by default. You can prevent that by passing an empty expand value to retrieve an entity without any extra relations.
For example:
import React from "react"
import { useAdminProducts } from "medusa-react"
const Products = () => {
const { products, isLoading } = useAdminProducts({
expand: "",
})
return (
<div>
{isLoading && <span>Loading...</span>}
{products && !products.length && <span>No Products</span>}
{products && products.length > 0 && (
<ul>
{products.map((product) => (
<li key={product.id}>{product.title}</li>
))}
</ul>
)}
</div>
)
}
export default Products
This would retrieve each product with only its properties, without any relations like collection
.
Selecting Fields
In many hooks you'll find a fields
property that can be accepted within one of the hooks's parameters. You can use the fields
property to specify which
fields in the entity should be returned in the response.
If you pass a fields
query parameter, only the fields you pass in the value along with the id
of the entity will be returned in the response.
The fields
query parameter does not affect the expanded relations. You'll have to use the Expand parameter instead.
Selecting One Field
For example, when you retrieve a list of products, you can retrieve only the titles of the products by passing title
as a value to the fields
query parameter:
import React from "react"
import { useAdminProducts } from "medusa-react"
const Products = () => {
const { products, isLoading } = useAdminProducts({
fields: "title",
})
return (
<div>
{isLoading && <span>Loading...</span>}
{products && !products.length && <span>No Products</span>}
{products && products.length > 0 && (
<ul>
{products.map((product) => (
<li key={product.id}>{product.title}</li>
))}
</ul>
)}
</div>
)
}
export default Products
As mentioned above, the expanded relations such as variants
will still be returned as they're not affected by the fields
parameter.
You can ensure that only the title
field is returned by passing an empty value to the expand
query parameter. For example:
import React from "react"
import { useAdminProducts } from "medusa-react"
const Products = () => {
const { products, isLoading } = useAdminProducts({
fields: "title",
expand: "",
})
return (
<div>
{isLoading && <span>Loading...</span>}
{products && !products.length && <span>No Products</span>}
{products && products.length > 0 && (
<ul>
{products.map((product) => (
<li key={product.id}>{product.title}</li>
))}
</ul>
)}
</div>
)
}
export default Products
Selecting Multiple Fields
You can pass more than one field by separating the field names in the fields
query parameter with a comma.
For example, to select the title
and handle
of products:
import React from "react"
import { useAdminProducts } from "medusa-react"
const Products = () => {
const { products, isLoading } = useAdminProducts({
fields: "title,handle",
})
return (
<div>
{isLoading && <span>Loading...</span>}
{products && !products.length && <span>No Products</span>}
{products && products.length > 0 && (
<ul>
{products.map((product) => (
<li key={product.id}>{product.title}</li>
))}
</ul>
)}
</div>
)
}
export default Products
Retrieve Only the ID
You can pass an empty fields
query parameter to return only the ID of an entity.
For example:
import React from "react"
import { useAdminProducts } from "medusa-react"
const Products = () => {
const { products, isLoading } = useAdminProducts({
fields: "",
})
return (
<div>
{isLoading && <span>Loading...</span>}
{products && !products.length && <span>No Products</span>}
{products && products.length > 0 && (
<ul>
{products.map((product) => (
<li key={product.id}>{product.id}</li>
))}
</ul>
)}
</div>
)
}
export default Products
You can also pair with an empty expand
query parameter to ensure that the relations aren't retrieved as well. For example:
import React from "react"
import { useAdminProducts } from "medusa-react"
const Products = () => {
const { products, isLoading } = useAdminProducts({
fields: "",
expand: "",
})
return (
<div>
{isLoading && <span>Loading...</span>}
{products && !products.length && <span>No Products</span>}
{products && products.length > 0 && (
<ul>
{products.map((product) => (
<li key={product.id}>{product.id}</li>
))}
</ul>
)}
</div>
)
}
export default Products
Pagination
Query Parameters
In listing hooks, such as list customers or list products, you can control the pagination using the query parameters limit
and offset
.
limit
is used to specify the maximum number of items that can be return in the response. offset
is used to specify how many items to skip before returning the resulting entities.
You can use the offset
query parameter to change between pages. For example, if the limit is 50
, at page one the offset should be 0
; at page two the offset should be 50
, and so on.
For example, to limit the number of products retrieved:
import React from "react"
import { useAdminProducts } from "medusa-react"
const Products = () => {
const {
products,
limit,
offset,
isLoading,
} = useAdminProducts({
limit: 20,
offset: 0,
})
return (
<div>
{isLoading && <span>Loading...</span>}
{products && !products.length && <span>No Products</span>}
{products && products.length > 0 && (
<ul>
{products.map((product) => (
<li key={product.id}>{product.title}</li>
))}
</ul>
)}
</div>
)
}
export default Products
Response Fields
In the response of listing hooks, aside from the entities retrieved, there are three pagination-related fields returned:
limit
: the maximum number of items that can be returned in the response.offset
: the number of items that were skipped before the entities in the result.count
: the total number of available items of this entity. It can be used to determine how many pages are there.
For example, if the count
is 100
and the limit
is 50
, you can divide the count
by the limit
to get the number of pages: 100/50 = 2 pages
.
Sort Order
The order
field, available on hooks supporting pagination, allows you to sort the retrieved items by an attribute of that item. For example, you can sort products by their created_at
attribute by setting order
to created_at
:
import React from "react"
import { useAdminProducts } from "medusa-react"
const Products = () => {
const {
products,
isLoading,
} = useAdminProducts({
order: "created_at",
})
return (
<div>
{isLoading && <span>Loading...</span>}
{products && !products.length && <span>No Products</span>}
{products && products.length > 0 && (
<ul>
{products.map((product) => (
<li key={product.id}>{product.title}</li>
))}
</ul>
)}
</div>
)
}
export default Products
By default, the sort direction will be ascending. To change it to descending, pass a dash (-
) before the attribute name. For example:
import React from "react"
import { useAdminProducts } from "medusa-react"
const Products = () => {
const {
products,
isLoading,
} = useAdminProducts({
order: "-created_at",
})
return (
<div>
{isLoading && <span>Loading...</span>}
{products && !products.length && <span>No Products</span>}
{products && products.length > 0 && (
<ul>
{products.map((product) => (
<li key={product.id}>{product.title}</li>
))}
</ul>
)}
</div>
)
}
export default Products
This sorts the products by their created_at
attribute in the descending order.