Article brought to you by buildnextshop.com
This is a continuation to the Shopify + Next.js + Tailwind CSS article that I wrote in September 2021. It already has over 7000 views!
If you have been using React, you probably already know that Next.js is a pretty sweet framework that provides a bunch of features on top of React. Some of the popular ones include: SSR (Server-side Rendering), SSG (Static-site Generation), simplified dynamic routing, easy deployment with Vercel, and much more.
In this article I would like to introduce you to SWR (stale-while-revalidate) package that is also created by the Vercel team.
SWR allows us to add CSR (Client-side rendering) to our static pages generated by Next.js.
So why would we want to add SWR? Well, SSG pages give us a great speed advantage, which is super important in e-commerce. But it also has a disadvantage that we need to rebuild and redeploy any changes that happen to the static pages. This becomes a problem when we want to update small components of our app.
I think this example will give you a good understanding of the power of SWR so let's dive right in!
To follow along with this example, you will need to:
- set up a Shopify private app (you can refer to this youtube tutorial on our channel)
- clone this Github repository
1. Setting up our Next.js /api/available.js file
The /api folder in Next.js is a bit like magic. It allows us to build an API endpoint right in our frontend application. They are server-side only bundles and won't increase your client-side bundle size.
build-next-shop
┣ lib
┣ node_modules
┣ pages
┃ ┗ api
┃ ┗ hello.js *
┣ public
┣ .env.local
┗ package.json
....
Let's remove everything in the hello.js
file, rename it to available.js
and paste in this code:
export default async function available(req, res) {
const { query: { id } } = req
const domain = process.env.SHOPIFY_STORE_DOMAIN
const storefrontAccessToken = process.env.SHOPIFY_STOREFRONT_ACCESSTOKEN
async function ShopifyData(query) {
const URL = `https://${domain}/api/2021-07/graphql.json`
const options = {
endpoint: URL,
method: "POST",
headers: {
"X-Shopify-Storefront-Access-Token": storefrontAccessToken,
"Accept": "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({ query })
}
try {
const data = await fetch(URL, options).then(response => {
return response.json()
})
return data
} catch (error) {
throw new Error("Products not fetched")
}
}
async function getProduct(handle) {
const query = `
{
productByHandle(handle: "${handle}") {
id
variants(first: 25) {
edges {
node {
id
availableForSale
}
}
}
}
}`
const response = await ShopifyData(query)
const product = response.data.productByHandle ? response.data.productByHandle : []
return product
}
const products = await getProduct(id)
res.status(200)
res.json(products)
}
So what is happening in this code?
We are creating and exporting and async function
available
with two parameters: request and response.We are deconstructing the
req.query.id
variable to get theid
Grabbing our secret values from our .env file and assigning them to domain and storefrontAccessToken variables.
Setting an options variable that contains our Shopify Graphql URL, method of request, headers, and our query body.
We created a
getProduct
function that receives a handle (which we called id in our case).We save the results of our getProduct function into a
products
variable.We return the products variable in json format to our Next.js component that calls the API.
2. Updating our ProductForm.js component
build-next-shop
┣ .next
┣ components
...
┃ ┗ ProductForm.js
┣ context
┣ lib
┣ node_modules
┗ pages
....
Let's import useSWR from "swr"
and import axios from "axios"
add our custom fetcher function at the top of the component
import useSWR from "swr"
import axios from "axios"
const fetcher = (url, id) => (
axios.get(url, {
params: {
id: id
}
}).then((res) => res.data)
)
This function replaces the default swr fetcher function and replaces it with axios (read more about it here)
Then we will use the useSWR hook inside of our component:
...
export default function ProductForm({ product }) {
const { data: productInventory } = useSWR(
['/api/available', product.handle],
(url, id) => fetcher(url, id),
{ errorRetryCount: 3 }
)
...
// rest of the component not shown
Now we can console.log(productInventory)
and grab the data from our API!
3. Add a useEffect hook to update our state
Let's add a new state:
const [available, setAvailable] = useState(true)
and then we can update it with our useEffect hook like this:
useEffect(() => {
if (productInventory) {
const checkAvailable = productInventory?.variants.edges.filter(item => item.node.id === selectedVariant.id)
if (checkAvailable[0].node.availableForSale) {
setAvailable(true)
} else {
setAvailable(false)
}
}
}, [productInventory, selectedVariant])`
First we are checking if productInventory
was fetched. Then we find the variant that is selected and search for it in our productInventory
variable and update our button state based on the result.
4. Update the button UI based on the availability like this:
return (
<div className="rounded-2xl p-4 shadow-lg flex flex-col w-full md:w-1/3">
<h2 className="text-2xl font-bold">{product.title}</h2>
<span className="pb-3">{formatter.format(product.variants.edges[0].node.priceV2.amount)}</span>
{
product.options.map(({ name, values }) => (
<ProductOptions
key={`key-${name}`}
name={name}
values={values}
selectedOptions={selectedOptions}
setOptions={setOptions}
/>
))
}
{
available ?
<button
onClick={() => {
addToCart(selectedVariant)
}}
className="bg-black rounded-lg text-white px-2 py-3 mt-3 hover:bg-gray-800">
Add To Card
</button>
:
<button
className="rounded-lg text-white px-2 py-3 mt-3 bg-gray-800 cursor-not-allowed">
Sold out!
</button>
}
</div>
)
Here we create a ternary to check our available state and choose which button to display based on the boolean value.
I hope you enjoyed the tutorial!
Sample Starter Project: https://github.com/iskurbanov/shopify-next.js-tailwind
Checkout the example website and full tutorial at BuildNextShop.com where we create a fully production ready Shopify Headless store using Next.js!
Top comments (0)