Build an online Shop with Gatsby and Shopify (Part 2)

Build an online Shop with Gatsby and Shopify (Part 2)

2021-05-20

In the last part we learned how to get data from shopify. This helps us to show the customer our data. But this is only one way. We only can show data and not interact with it. So what would happen if a user wants to buy an Item from our store. Right now it would not work. But in this series we will make it work. So fasten your seatbelt we will start right now.

Shopify Buy SDK

For the interaction you need the shopify buy SDK. I totally recommend you to have a look at the documentation. There a lot more things you could do with the SDK than we can cover here.

First you need to install the SDK.

npm install shopify-buy

We also need isomorphic-fetch. You might know what fetch is, but what is it, and why do we need it? The problem with fetch is that is currently not implemented across all browsers consistently. Isommorphic-Fetch allows you to fetch in your node code in a cross-browser compliant fashion. So it is better to use it here than fetch.

So the next step would be to also install this.

npm install isomorphic-fetch

Now we are ready to start coding. So grab your favorite drink and lets start.

Alt Text

React Context

So the first thing would be to initialise the newly installed SDK. But where do we place it? It should be accessible everywhere, and it would be nice if we could abstract the logic to a separate file. Would the layout component be right? No I guess that would not be good. Maybe you have a page which won't use the layout. Then you could not use the SDK.

I would recommend using the React context. I will explain shortly what this is. If you are familiar with it feel free to jump to the part where we initialise the SDK.

The context allows us to pass data through the component tree without having to pass props down to every component by hand. That is awesome because it could be really annoying to pass the props all the time to your components. You can learn more about it in the official documentation.

So the next step would be to add a folder called provider with one file in it called ContextProvider.js. At the top you need to import three things.

import fetch from "isomorphic-fetch"
import React, { useEffect, createContext } from "react"
import Client from "shopify-buy"

Let's now continue with initializing the shopify-buy SDK.

Initialize the Shopify Buy SDK.

const client = Client.buildClient(
{
storefrontAccessToken: process.env.ACCESS_TOKEN,
domain: `${process.env.SHOP_NAME}`,
},
fetch
)

With the build client function we can initialize the client. You have to pass your storefront token and your domain name. And also the isomorphic fetch as a second argument. Afterwards the client is ready to use.

Next we should set some default values the application could use to set up the store. Just create a constant under the initialized client.

const defaultValues = {
client,
checkout: {
lineItems: [],
},
}

The client is the client we initialized before. And then we also have a checkout object which contains an array of lineItems. This is the array which will store all the items a user puts into his shopping bag.

The next step would be to create the context and really important export it. We should export it that we could use it in our component tree. But this will be done later but add the line to your file.

export const StoreContext = createContext(defaultValues)

The createContext function was imported from react above and is a function that comes with react. We pass the default values to give create the context with the values we defined. Nothing special here.

Another helper we add under the const is a check if we are in a browser. Why do we need this? Gatsby is server-side rendered and at some points we may need to access the window element. On the server we wont have a window object because it comes with the browsers. So the helper show return true if we are in a browser.

const isBrowser = typeof window !== "undefined"

So we check the type of the window property. If window would be undefined we could not access it, and we might be not in the browser. We need later to access the localStorage, and therefore we need a window object. If we would not check this the operation could potentially fail.

Let's have a look how your ContextProvider file would look right now.

import fetch from "isomorphic-fetch"
import React, { useEffect, createContext, useState } from "react"
import Client from "shopify-buy"
const client = Client.buildClient(
{
storefrontAccessToken: process.env.ACCESS_TOKEN,
domain: `${process.env.SHOP_NAME}`,
},
fetch
)
const defaultValues = {
client,
checkout: {
lineItems: [],
},
}
export const StoreContext = createContext(defaultValues)
const isBrowser = typeof window !== "undefined"

Create the Context Component

Awesome now we finally can build the ContextProvider Component with all the logic in it.

const ContextProvider = ({ children }) => {
const [checkout, setCheckout] = useState(defaultValues.checkout)
return <StoreContext.Provider>{children}</StoreContext.Provider>
}
export default ContextProvider

Ok that's a lot at once. I give my best to explain it as easy as possible. In the first line we create the component and pass a child into the component. The children allow us to use the component later as a wrapper component. Like so:

<ContextProvider>
<AnyChildComponent />
</ContextProvider>

But we need to pass it somewhere in our return value from the component.

In the first line of our functional component we defined a State with the useState hook. We pass to properties to it. One is the checkout. With this we could use the values stored in the checkout. And the others would be setCheckout which allows us to change the values which are store in the checkout. And we pass the hook our default values for the checkout.

Why is this useful? There may be the case where you want to access all your items in the shopping cart. But the customer did not put one item in there. So your store would crash because you tried to map over all items in your bag, but there is emptiness inside and no array you could use for mapping over.

The last thing we have to talk about here is the return value. We return the StoreContext we create before the component and access the provider from it. That's how we use the context. Don't worry too much about it.

The next step is to initialize the checkout when we want the shop is mounted. To achieve this we use the useEffect hook from react to be able to do some things when the shop is mounted.

Use Effect Hook

The useEffect hook is also a react function which can be used to access the lifecycle of our application.

useEffect(() => {
const initializeCheckout = async () => {
const existingCheckoutID = isBrowser
? localStorage.getItem("shopify_checkout_id")
: null
if (existingCheckoutID && existingCheckoutID !== `null`) {
try {
const existingCheckout = await client.checkout.fetch(existingCheckoutID)
if (!existingCheckout.completedAt) {
setCheckoutItem(existingCheckout)
return
}
} catch (e) {
localStorage.setItem("shopify_checkout_id", null)
}
}
const newCheckout = await client.checkout.create()
setCheckoutItem(newCheckout)
}
initializeCheckout()
}, [])

Again it`s a lot and I try my best to explain it for now.

First we use the useEffect hook which we need to pass a function as the first argument. In this case it is an anonymous arrow function. It basically looks like this () => {}. Weird syntax I think. Anyways.

Next up we want to initialize the checkout. So you might think you could directly write all the code inside the arrow function. That's sadly not that easy. As we talk to the serve we need to write an async function. Because it could take some time to wait for answer from the server. When we want to do something async in the useEffect hook we need to write is as a separate function. Otherwise, it won't work. So we should write an arrow function which is marked as async.

In the first line of the function we check if there is already a checkoutId. The checkoutId is used by shopify to save different shopping bags. Every bag that is created gets a unique id which is used to access it later and recognize the items a user picked. We do the check because we want to present the user a good experience. Maybe he returns to the page after a few hours and then wants to buy the items. Then we still could recognize what he picked. Also, if he would switch the sites in our shop he would always use the items in his bag. That would be a bad shopping experience.

For the check we create a variable where could store the value of the existing checkoutId. First we check if we are in a browser otherwise we cannot access the local storage. If we aren't in a browser it would be set to null. Then we get the localStorage item by the name we defined.

Short coffee break to get some energy for the next steps. coffe break

Ok back full off energy.

We next check if we have a checkoutId, and it also should not be null. Remember if it would be null we would be on the server. Then we know there is already a checkout. So the user put some things in a basket. Then we need to get the things stored in there. So we need to talk to shopify and get the data. But this potentially could go wrong, so we will wrap it in a try catch block.

Next we use the shopify SDK to fetch the checkout. We pass the id there and then store it in a constant. We need to use await because it could take some time to talk to the server.

After we got the answer we need to use another if statement to check it the checkoutId was not already used to check out. If this is not the case we could set the checkout item and return which stops the function.

The setCheckoutItem is a little helper function which I created. It looks like the following and has to be declared above the useEffect.

const setCheckoutItem = checkout => {
if (isBrowser) {
localStorage.setItem("shopify_checkout_id", checkout.id)
}
setCheckout(checkout)
}

It takes the checkout obIt takes the checkout object and first stores the checkoutId into the localStorage for later and also sets the checkout state. That's needed because we use it at each new mount of the shop as described earlier.

Now we need to go back again to the function inside the useEffect hook. At the bottom of the initializeCheckout function are two more lines I would like to point your attention to. If there would not be a checkoutId we have to create a new checkout. Therefore, we again use the shopify sdk to create it and store it into a constant. And then again call our little helper function. So this is needed when a customer visits our store for the first time or after a long time again.

And then we are done with the initalize function. So we need to call it inside the use effect hook. One important thing you should not forget is to add the empty array as second argument to the useEffect hook. This ensures that the hook is only run on the first render.

Wow. That was a lot. Here is the file again in total now.

import fetch from "isomorphic-fetch"
import React, { useEffect, createContext, useState } from "react"
import Client from "shopify-buy"
const client = Client.buildClient(
{
storefrontAccessToken: process.env.ACCESS_TOKEN,
domain: `${process.env.SHOP_NAME}`,
},
fetch
)
const defaultValues = {
client,
checkout: {
lineItems: [],
},
}
export const StoreContext = createContext(defaultValues)
const isBrowser = typeof window !== "undefined"
const ContextProvider = ({ children }) => {
const [checkout, setCheckout] = useState(defaultValues.checkout)
const setCheckoutItem = checkout => {
if (isBrowser) {
localStorage.setItem("shopify_checkout_id", checkout.id)
}
setCheckout(checkout)
}
useEffect(() => {
const initializeCheckout = async () => {
const existingCheckoutID = isBrowser
? localStorage.getItem("shopify_checkout_id")
: null
if (existingCheckoutID && existingCheckoutID !== `null`) {
try {
const existingCheckout = await client.checkout.fetch(
existingCheckoutID
)
if (!existingCheckout.completedAt) {
setCheckoutItem(existingCheckout)
return
}
} catch (e) {
localStorage.setItem("shopify_checkout_id", null)
}
}
const newCheckout = await client.checkout.create()
setCheckoutItem(newCheckout)
}
initializeCheckout()
}, [])
return <StoreContext.Provider>{children}</StoreContext.Provider>
}
export default ContextProvider

So that's the end for now. It was a lot to cover, but now we have the foundation for our interaction with the shop setup. In the next steps would be to actually use the context and add items to the shopping bag. Afterwards we will build a shopping back component. Sounds like fun? Stay tuned for the next part.

Test your knowledge!

What does React Context allows us to do?

Points: 0 / 2

update our dependencies

paint the wall of our office

send a mail to our customer

pass data through the component tree

thats me
Thanks for reading!

Hey 👋 thanks a lot for reading. If there is anything web dev related you want to discuss with me or if there is anything I could help you with, just drop me a message atLinkedIn, Twitter or via Mail.
I am also available for freelance work, just hit me up! And if you like what you read you would do me a favor if you share it. 🙌