Gatsby Theme Shopify Manager
Gatsby Theme Shopify Manager is a Gatsby theme that manages the data connections between Shopify and your Gatsby storefront. All you have to do is:
- Install the theme
- Provide API credentials
- Import hooks
And then start coding. 🚀
This is a data theme, not a UI theme. It makes setting up a Shopify cart and buyer flow simple (including managing state), so you can focus on making it look great.
Want to see this in action? This page is a working example, as well as documentation. Read on to see it!
Getting Started
To start using the theme, install it with your package manager of choice:
yarn add gatsby-theme-shopify-manager gatsby-source-shopify
To start using it, open your gatsby-config
file and include your Shop name and access token from the Storefront API.
{
resolve: `gatsby-theme-shopify-manager`,
options: {
shopName: 'your shop name',
accessToken: 'your storefront API access token',
},
},
The options you pass to this theme are used to configure both gatsby-source-shopify
and the shopify-buy client.
Configuration options
There are four options to configure this theme, with only the first two being required.
In case you're looking for a quick copy and paste 👇:
{
resolve: `gatsby-theme-shopify-manager`,
options: {
shopName: 'your-shop-name', // or custom domain
accessToken: 'your-api-access-token',
shouldConfigureSourcePlugin: true, // default
shouldWrapRootElementWithProvider: true, // default
},
},
shopName
This is the first part of the default Shopify domain. If your domain is my-store.myshopify.com
, the shopName would be my-store
. This value is required unless you pass false
to both shouldConfigureSourcePlugin
and shouldWrapRootElementWithProvider
.
If you're using a custom domain with Shopify, you should enter your custom domain instead (e.g. mystore.com
). Make sure to only include the name and domain, and omit the protocol (http
) and any trailing slashes.
{
resolve: `gatsby-theme-shopify-manager`,
options: {
shopName: 'my-store', // or mystore.com
},
},
accessToken
This is the Storefront API token that you get when you make a new Shopify app. This value is required unless you pass false
to both shouldConfigureSourcePlugin
and shouldWrapRootElementWithProvider
.
{
resolve: `gatsby-theme-shopify-manager`,
options: {
accessToken: '12lg(@!l129gj12p['
},
},
shouldConfigureSourcePlugin
By default, gatsby-theme-shopify-manager
passes a configuration object to the gatsby-source-shopify
plugin in gatsby-config
. If you need to do advanced configuration of that plugin, pass false
to this option. From there, you can set up and configure your source plugin as you please.
{
resolve: `gatsby-theme-shopify-manager`,
options: {
// default value is true
shouldConfigureSourcePlugin: false
},
},
shouldWrapRootElementWithProvider
By default, gatsby-theme-shopify-manager
wraps the application in a <ContextProvider>
, and passes the shopName
and accessToken
provided to the theme options through to the provider. Pass false
to this option to prevent this behavior.
{
resolve: `gatsby-theme-shopify-manager`,
options: {
// default value is true
shouldWrapRootElementWithProvider: false
},
},
Context Provider
The Shopify buy client and current cart state are managed using React context. By default, the application is wrapped by the Provider and the shopName
and accessToken
are pulled from the config options and passed to it. However, in some cases, it might be preferable to manage the provider.
By default,
gatsby-theme-shopify-manager
wraps the application in a provider. If you want to manage this yourself, passshouldWrapRootElementWithProvider: false
to the theme options. If you don't, you'll have multiple providers that may result in unintended side-effects.
To use the provider, import it and pass shopName
and accessToken
to it as props.
import React from 'react';
import {ContextProvider} from 'gatsby-theme-shopify-manager';
export const App = ({children}) => {
const shopName = 'some-shop-name';
const accessToken = 'some-access-token';
return (
<ContextProvider shopName={shopName} accessToken={accessToken}>
{children}
</ContextProvider>
);
};
Hooks
The main export of this package are the hooks that you can use. Here are the hooks you can use:
- useCart
- useCartItems
- useCartCount
- useCheckoutUrl
- useAddItemsToCart
- useAddItemToCart
- useRemoveItemsFromCart
- useRemoveItemFromCart
- useUpdateItemQuantity
useCart()
The most basic hook is the useCart()
hook. This hook gives you access to the current cart state (or null, if there is no cart state). From this object you can get access to the line items, the total amounts, and additional checkout-related information.
The cart object is currently null.
import React from 'react';
import {useCart} from 'gatsby-theme-shopify-manager';
export function ExampleUseCart() {
const cart = useCart();
if (cart == null) {
return <p>The cart object is currently null.</p>;
}
const cartDate = new Date(cart.createdAt).toLocaleDateString();
return (
<p>
Your cart was created on {cartDate}.
<br />
You have ${cart.totalPrice} worth of products in your cart.
</p>
);
}
useCartItems()
The useCartItems()
hook provides access to the items currently in the cart. This hook always returns an array.
Your cart is empty.
import React from 'react';
import {useCartItems} from 'gatsby-theme-shopify-manager';
export function ExampleUseCartItems() {
const cartItems = useCartItems();
if (cartItems.length < 1) {
return <p>Your cart is empty.</p>;
}
return (
<>
<p>Your cart has the following items:</p>
<ul>
{cartItems.map((lineItem) => (
<li key={lineItem.title}>
{lineItem.title} - {lineItem.variant.title}
</li>
))}
</ul>
</>
);
}
useCartCount()
The useCartCount()
hook provides the number of items currently in the cart. This hook returns 0 if the cart is null (and will always return a number). This method does not return just cartItems.length
, but sums the quantity of each variant in the cart.
Your cart has 0 items.
import React from 'react';
import {useCartCount} from 'gatsby-theme-shopify-manager';
export function ExampleUseCartCount() {
const cartCount = useCartCount();
return <p>Your cart has {cartCount} items.</p>;
}
useCheckoutUrl()
The useCheckoutUrl()
hook provides the checkout url that is associated with the current cart. It returns null
when the cart is null
, and otherwise returns a string
.
There is no active checkout.
import React from 'react';
import {useCheckoutUrl} from 'gatsby-theme-shopify-manager';
export function ExampleUseCheckoutUrl() {
const checkoutUrl = useCheckoutUrl();
return checkoutUrl == null ? (
<p>There is no active checkout.</p>
) : (
<p>
<a href={checkoutUrl} target="_blank" rel="noopener noreferrer">
Complete Your Order →
</a>
</p>
);
}
useAddItemsToCart()
The useAddItemsToCart
hook allows you to add multiple items to the cart at a single time. The hook returns a function that accepts an array of objects with keys variantId
and quantity
. It returns a void
promise that will throw if it encounters an error. You can optionally include an array of customAttributes
with each item.
There are currently 0 items in your cart.
import React from 'react';
import {Button} from 'theme-ui';
import {useAddItemsToCart, useCartCount} from 'gatsby-theme-shopify-manager';
export function ExampleUseAddItemsToCart() {
const cartCount = useCartCount();
const addItemsToCart = useAddItemsToCart();
async function addToCart() {
const items = [
{
variantId: 'some_variant_id',
quantity: 1,
},
];
try {
await addItemsToCart(items);
alert('Successfully added that item to your cart!');
} catch {
alert('There was a problem adding that item to your cart.');
}
}
return (
<>
<p>There are currently {cartCount} items in your cart.</p>
<Button onClick={addToCart}>
Add items to your cart
</Button>
</>
);
}
useAddItemToCart()
The useAddItemToCart
is similar to the useAddItemsToCart
, but is only for a single item at a time. The hook returns a function that accepts three arguments: variantId
, quantity
, and (optionally) an array of customAttributes
.
There are currently 0 items in your cart.
import React from 'react';
import {Button} from 'theme-ui';
import {useAddItemToCart, useCartCount} from 'gatsby-theme-shopify-manager';
export function ExampleUseAddItemToCart() {
const cartCount = useCartCount();
const addItemToCart = useAddItemToCart();
async function addToCart() {
const variantId = 'some_variant_id';
const quantity = 1;
try {
await addItemToCart(variantId, quantity);
alert('Successfully added that item to your cart!');
} catch {
alert('There was a problem adding that item to your cart.');
}
}
return (
<>
<p>There are currently {cartCount} items in your cart.</p>
<Button onClick={addToCart}>
Add an item to your cart
</Button>
</>
);
}
useRemoveItemsFromCart()
The useRemoveItemFromCart
hook allows you to remove multiple items from the cart at a single time. The hook returns a function that accepts an array of variantId
strings. It returns a void
promise that will throw if it encounters an error.
Your cart is empty.
import React from 'react';
import {Button} from 'theme-ui';
import {
useRemoveItemsFromCart,
useCartItems,
} from 'gatsby-theme-shopify-manager';
export function ExampleUseRemoveItemsFromCart() {
const cartItems = useCartItems();
const removeItemsFromCart = useRemoveItemsFromCart();
async function removeFromCart() {
if (cartItems.length < 1) {
return;
}
const variantId = cartItems[0].variant.id;
try {
await removeItemsFromCart([variantId]);
alert('Successfully removed an item from your cart!');
} catch {
alert('There was a problem removing that item from your cart.');
}
}
const cartMarkup =
cartItems.length > 0 ? (
<>
<p>Your cart has the following items:</p>
<ul>
{cartItems.map((lineItem) => (
<li key={lineItem.title}>
{lineItem.title} - {lineItem.variant.title}
</li>
))}
</ul>
</>
) : (
<p>Your cart is empty.</p>
);
return (
<>
{cartMarkup}
<Button onClick={removeFromCart} sx={{mb: 3}}>
Remove items from your cart
</Button>
</>
);
}
useRemoveItemFromCart()
The useRemoveItemFromCart
is similar to the useRemoveItemsFromCart
hook, but is only for a single item at a time. The hook returns a function that accepts a single argument: variantId
.
Your cart is empty.
import React from 'react';
import {Button} from 'theme-ui';
import {
useRemoveItemFromCart,
useCartItems,
} from 'gatsby-theme-shopify-manager';
export function ExampleUseRemoveItemFromCart() {
const cartItems = useCartItems();
const removeItemFromCart = useRemoveItemFromCart();
async function removeFromCart() {
if (cartItems.length < 1) {
return;
}
const variantId = cartItems[0].variant.id;
try {
await removeItemFromCart(variantId);
alert('Successfully removed an item from your cart!');
} catch {
alert('There was a problem removing that item from your cart.');
}
}
const cartMarkup =
cartItems.length > 0 ? (
<>
<p>Your cart has the following items:</p>
<ul>
{cartItems.map((lineItem) => (
<li key={lineItem.title}>
{lineItem.title} - {lineItem.variant.title}
</li>
))}
</ul>
</>
) : (
<p>Your cart is empty.</p>
);
return (
<>
{cartMarkup}
<Button onClick={removeFromCart} sx={{mb: 3}}>
Remove item from your cart
</Button>
</>
);
}
useUpdateItemQuantity()
The useUpdateItemQuantity()
hook returns a function that updates the quantity of a lineitem currently in the cart. The returned function accepts two arguments: variantId
and quantity
. It returns a void
Promise that throws if it encounters an error. If 0
is passed in as the quantity, it removes the item from the cart.
Your cart is empty.
import React, {useState} from 'react';
import {Flex, Button, Input} from 'theme-ui';
import {
useUpdateItemQuantity,
useCartItems,
} from 'gatsby-theme-shopify-manager';
export function ExampleUseUpdateItemQuantity() {
const [quantity, setQuantity] = useState(1);
const [item] = useCartItems();
const updateItemQuantity = useUpdateItemQuantity();
async function updateQuantity() {
if (item == null) {
return;
}
const variantId = item.variant.id;
try {
await updateItemQuantity(variantId, quantity);
alert('Successfully updated the item quantity!');
} catch {
alert("There was a problem updating that item's quantity.");
}
}
const itemMarkup =
item == null ? (
<p>Your cart is empty.</p>
) : (
<p>
{item.title} - {item.variant.title} ({item.quantity})
</p>
);
const formMarkup = (
<Flex sx={{alignItems: 'flex-start'}} as="form" onSubmit={updateQuantity}>
<Input
sx={{width: '50px', mr: 3}}
type="number"
defaultValue={quantity}
onChange={(event) => setQuantity(Number(event.target.value))}
/>
<Button sx={{mb: 3}}>Update quantity</Button>
</Flex>
);
return (
<>
{itemMarkup}
{formMarkup}
</>
);
}
Escape Hooks
In addition to the normal hooks, there are two 'escape' hooks. These hooks allow access to setting the cart state and the client object that is used to interact with Shopify. It's important to note that these are considered experiemental–using these hooks may result in unintended side-effects.
useClientUnsafe
The useClientUnsafe
hook returns the client object currently held in the context. From there you can call methods on it to enable more functionality. Shopify has all the documentation for what you can do with the client object. Example usage:
import React from 'react';
import {useClientUnsafe} from 'gatsby-theme-shopify-manager';
export function ExampleUseClientUnsafe() {
const client = useClientUnsafe();
// do work with the client here
}
useSetCartUnsafe
The useSetCartUnsafe
returns a function that allows the user to set the current cart state. You can use it similar to the function returned from a useState
destructure. This is useful for interactions with the client
object that return an updated cart object. Example usage:
import React from 'react';
import {useClientUnsafe, useSetCartUnsafe} from 'gatsby-theme-shopify-manager';
export function ExampleUseSetCartUnsafe() {
const client = useClientUnsafe();
const setCart = useSetCartUnsafe();
async function changeCart() {
const newCart = await client.doSomeMethodThatReturnsACartObject();
setCart(newCart);
}
changeCart();
}
Examples
Only Down is an example site that shows how to use the gatsby-theme-shopify-manager plugin.
Contributing & Issues
Want to add a feature, or report a bug? Head over to the GitHub repo to jump in!