This repository serves as a model to build your own hooks for Next.js Commerce. We encourage contributors and teams to build their own hooks under a separate package with the corresponding configuration. An example of using this repository as a seed are the hooks we built with BigCommerce.
The root folder contains an index.tsx
file with the CommerceProvider to set up the right commerce context. Additional components needed to run the framework need to be there.
src
contains all the handlers used by the whole framework. The rest of the folders in the root file are named by functionality, e.g cart, auth, wishlist and are the API of the framework. Therefore, most of the work should be done in the src
folder. Otherwise, it could affect the usage of the framework altering its functionality and possibly adding unexpected output.
The main configuration provider that creates the Commerce context. Used by all Commerce hooks. Every provider may define its own defaults.
import { CommerceProvider } from "@commerce-framework";
const App = ({ locale = "en-US", children }) => (
<CommerceProvider locale={locale}>{children}</CommerceProvider>
);
locale:
Locale to use for i18n. This is required.cartCookie:
Name of the cookie that saves the cart id. This is optional.fetcher:
Global fetcher function that will be used for every API call to a GraphQL or REST endpoint in the browser, it's in charge of error management on network errors. Usage of this config is optional.
CommerceProvider should not re-render unless a prop changes, and the only prop that's expected to change in most apps is
locale
.
Returns the configs (including defaults) that are defined in the nearest CommerceProvider
.
import { useCommerce } from "@commerce-framework/hooks";
const commerce = useCommerce();
May be useful if you want to create another CommerceProvider
and extend its options.
Returns the logged-in customer and its data.
import { useCustomer } from "@commerce-framework/hooks";
const customer = useCustomer();
const { data, error, loading } = customer;
if (error) return <p>There was an error: {error.message}</p>;
if (loading) return <p>Loading...</p>;
return <Page customer={data} />;
The customer is revalidated by operations executed by useLogin
, useSignup
and useLogout
.
Returns a function that allows a visitor to login into its customer account.
import { useLogin } from "@commerce-framework/hooks";
const LoginView = () => {
const login = useLogin();
const handleLogin = async () => {
await login({
email,
password,
});
};
return <form onSubmit={handleLogin}>{children}</form>;
};
Returns a function that allows the current customer to log out.
import { useLogout } from "@commerce-framework/hooks";
const LogoutLink = () => {
const logout = useLogout();
return <a onClick={() => logout()}>Logout</a>;
};
Returns a function that allows a visitor to sign up into the store.
The signup operation returns null
.
import { useSignup } from "@commerce-framework/hooks";
const SignupView = () => {
const signup = useSignup();
const handleSignup = async () => {
await signup({
email,
firstName,
lastName,
password,
});
};
return <form onSubmit={handleSignup}>{children}</form>;
};
Formats an amount--usually the price of a product-- into an internationalized string. It uses the current locale.
import { usePrice } from "@commerce-framework/hooks";
const { price, basePrice, discount } = usePrice({
amount: 100,
baseAmount: 50,
currencyCode: "USD",
});
usePrice
receives an object with:
amount:
A valid number, usually the sale price of a product. This is required.baseAmount:
A valid number, usually the listed price of a product. If it's higher thanamount
then the product is on sale andusePrice
will return the discounted percentage. This is optional.currencyCode:
The currency code to use. This is required.
usePrice
returns an object with:
price:
The formatted price of a product.basePrice:
The formatted base price of the product. Only present ifbaseAmount
is set.discount:
The discounted percentage. Only present ifbaseAmount
is set.
Returns the current cart data.
import { useCart } from "@commerce-framework/hooks";
const { data, error, isEmpty, loading } = useCart();
Returns a function that when called adds a new item to the current cart.
The addItem
operation returns the updated cart.
import { useAddItem } from "@commerce-framework/hooks";
const AddToCartButton = ({ productId, variantId }) => {
const addItem = useAddItem();
const addToCart = async () => {
await addItem({
productId,
variantId,
});
};
return <button onClick={addToCart}>Add To Cart</button>;
};
Returns a function to update an item of the current cart.
const updateItem = useUpdateItem();
await updateItem({
id,
productId,
variantId,
quantity,
});
// You can optionally send the item to the hook:
const updateItem = useUpdateItem(item);
// And now only the quantity is required
await updateItem({
quantity,
});
Returns a function that when called removes an item from the current cart.
const removeItem = useRemoveItem();
await removeItem({
id,
});
The removeItem
operation returns the updated cart.
The cart is deleted if the last remaining item is removed.
Returns the current wishlist of the logged in user.
import { useWishlist } from "@commerce-framework/hooks";
const { data } = useWishlist();
- getItem
- getAllItems
- useSearch
Data fetching looks like useCart
and useCustomer
return props to handle errors and loading states. For example, using the useCommerce
hook:
const customer = useCustomer();
const { data, error, loading } = customer;
if (error) return <p>There was an error: {error.message}</p>;
if (loading) return <p>Loading...</p>;
return <Page customer={data} />;
And using the useCart
hook:
const cart = useCart();
const { data, error, loading, isEmpty } = cart;
if (error) return <p>There was an error: {error.message}</p>;
if (loading) return <p>Loading...</p>;
if (isEmpty) return <p>The cart is currently empty</p>;
return <Page cart={data} />;
Hooks that execute user actions are async operations, you can track the loading state and errors using state hooks and try...catch. For example:
function LoginButton({ data }) {
const login = useLogin();
const [loading, setLoading] = useState(false);
const [errorMsg, setErrorMsg] = useState(false);
const handleClick = async () => {
// We're about to run the action, so set the state to loading
setLoading(true);
// Reset the error message if it failed before
setErrorMsg("");
try {
await login({
email: data.email,
password: data.password,
});
} catch (error) {
// Handle error codes specific for the `login` action
if (error.code === "invalid_credentials") {
setErrorMsg(
"Cannot find an account that matches the provided credentials"
);
} else {
setErrorMsg(error.message);
}
} finally {
setLoading(false);
}
};
return (
<div>
<button onClick={handleClick} disabled={loading}>
Add to Cart
</button>
{errorMsg && <p>{errorMsg}</p>}
</div>
);
}
Error codes depend on the hook that is being used. In this case invalid_credentials
is used by useLogin
.
All commerce related errors are an instance of CommerceError
.
All hooks that involve a fetch operation can be extended with a custom fetcher function. For example, if we wanted to extend the useCart
hook:
import useCartHook, { fetcher } from "commerce-lib/cart/use-cart";
const useCart = useCartHook.extend(
(options, input, fetch) => {
// Do something different
return fetcher(options, input, fetch);
},
{
// Optionally change the default SWR options
revalidateOnFocus: true,
}
);
// Then in your component, using your new hook works in the same way
const { data, error, isEmpty, updating } = useCart();
The extend
method is available for all hooks with fetch operations, it receives a fetcher function as the first parameter and SWR options as the second parameter if the hook uses SWR. extend
returns a new hook that works exactly as the hook it's extending from.
The fetcher function receives the following arguments:
options:
This is an object that may have aquery
(if the hook is using GraphQL), aurl
, and amethod
.input:
An object with the data that's required to execute the operation.fetch:
The fetch function that is set by theCommerceProvider
.