Üksikasjalik õpetus: kuidas kasutada Shopify Storefront API-d koos Reacti ja Reduxiga

E-kaubandus kõigile! (... veebisaidid, see on?)

Kirjutanud Chris august 2018, uuendatud november 2018

Taust ja motivatsioon

Nii et motivatsioon oli siin üsna lihtne. Tahtsin, et minu saidi külastajad saaksid sirvida, otsida ja valida tooteid otse minu kohandatud domeenis ilma meie Shopify saidile minemata.

Teiseks motivatsiooniks on see, et mul oleks palju parem oma veebisaidi jaoks oma koodibaas, kui et kasutada üht Shopify tehase malli. Shopify meeskond pole solvunud! Mallid on kaasaegsed ja puhtad, kuid on pigem põhilised. Olen kindel, et need mallid on tugevalt kohandatavad, kuid see pole hetkel teadaolev virn.

Nii et see on mõlema maailma parim - minu kohandatud React'i sait (juba ehitatud ja võrgus?), Millele on lisatud Shopify API ja kassaprotsess!

Selle õpetuse lõpuks saate oma Shopify tooted lisada oma saidi mis tahes lehele. Ainus osa Shopify-s toimuvast ostuprotsessist on see, kui kasutaja klõpsab nupul „Kassa“.

Olen selle õpetuse jaoks loonud ka tühja katlaplaadi hoidla.

Spetsiaalselt siin Mediumis kirjutamise ajendiks oli lihtsalt see, et ma ei suutnud ise selle protsessi kohta õpetust leida - nii otsustasin selle teha!

Olen olnud professionaalne arendaja juba neli aastat ja programmeerinud 7 aastat. Olen töötanud tehnikakogudes alates vana kooli Fortranist ja Perlist kuni Reactini, Javascripti, Pythoni ja Node'ini.

Siren Apparel on üks minu kõrvalprojektidest / idufirmadest / tegijatest, mida olen juhtinud juba 5 aastat, ja me oleme siiani annetanud 5 erinevale politsei- ja tuletõrjeosakonnale!

Alustame lõpuks selle juhendajaga.

Shopify poe API

Shopify suurepärased inimesed on kokku pannud Storefront API. Storefront API abil saate luua komponente React, et lisada oma enda mitte-Shopify saidile tootepilte, tootevariante, tootesuurusi, ostukorvi ning nuppe „Lisa ostukorvi“ ja „Kassasse“.

* Pange tähele, et see õpetus EI käi Shopify Polarise kohta, mida kasutatakse komponentide loomiseks React for Shopify poehalduses ise.

Alustamine: react-js-buyhoidla

Vaadake seda Shopify meeskonna loodud React'i näidet. Suurem osa selle õpetuse koodist pärineb sellest hoidlast.

... Kas viskasite pilgu peale? Hea! ?

Nüüd läheme otse koodi sisse! Minge oma React saidi juurkausta ja installige shopify-buymoodul terminali kaudu:

cd my-awesome-react-project/npm install --save shopify-buy

(või yarn add shopify-buykui soovite yarn)

Seejärel peate oma esiosas index.js(EI App.js!) ClientJS Buy SDK-st importima :

import Client from 'shopify-buy';

Seejärel lisage kõne kohale järgmine konfiguratsiooniobjekt ReactDOM.render():

const client = Client.buildClient({ storefrontAccessToken: 'your-access-token', domain: 'your-shopify-url.myshopify.com'});

Selleks korraks see on index.js- me tuleme selle juurde varsti tagasi.

Nüüd lisame kõik sujuva ostu- ja kassakogemuse jaoks vajalikud komponendid. Kopeerige kõik komponendid react-js-buyhoidlast:

Cart.js

LineItem.js

Product.js

Products.js

VariantSelector.js

Kleepime need komponendid components/shopify/teie src/kausta kausta. Soovi korral võite need komponendifailid src/kausta panna kuhugi mujale . Ülejäänud juhendaja eeldab, et olete need sisse pannud components/shopify/.

Rakenduse App.js muutmine

App.jsvajavad ulatuslikke muudatusi. Esmalt importige see ostukorvi komponent, mille äsja kopeerisite, oma projekti:

import Cart from './components/shopify/Cart';

Kui teie App.jskomponent oli kodakondsuseta, nagu ka minu, peaksite kogu selle constructor()funktsiooni turvaliselt kopeerima :

constructor() { super(); this.updateQuantityInCart = this.updateQuantityInCart.bind(this); this.removeLineItemInCart = this.removeLineItemInCart.bind(this); this.handleCartClose = this.handleCartClose.bind(this);}

Kui teil on juba olek, kopeerige ainult need bindread. Need kolm rida on sündmuste käitleja funktsioonid, mida Shopify ostukorv peab õigesti töötama.

"Aga kuidas on riik käru jaoks !?"

Võite küsida; või:

"Aga nende sündmuste käitlejate määratlemine käru jaoks !?"

Tõepoolest, see tuleb, aga mitte veel! ?

Seejärel saate selle lisada t/> component to the bottom of your render() function, before the ending div.

In my opinion, the cart should be accessible anywhere in your app. I think it makes sense, then, to put the t/> component in the root component of your app — in other w ords, App.js:

return ( ... );

Again, I haven’t included any code on the event handlers for the cart yet. Additionally, I didn’t address the lack of state components for the cart in App.js.

There is good reason for this.

About halfway through this project, I realized my products component was of course not in my App.js file.

Instead, it was buried about three children components down.

So instead of passing products three levels down to children, and then function handlers all the way back up…

I decided to use…

? Redux!!! ?

Ugh! I know, I know, Redux, while not being very difficult, is a pain in the %*$! to wire up initially with all the boilerplate required. But, if you are a developer working on an E-commerce store or an E-commerce store owner, think of it this way: Redux will enable you to access the state of the cart from any component or page in our website or webapp.

This ability will be essential as Siren Apparel expands and we develop more products. As we create more products, I’ll make a separate dedicated store page with all products, while leaving just a handful of featured products on the homepage.

The ability to access the cart is essential if a user shops around a bit, reads some stories or info about Siren Apparel, and then decides to checkout. It doesn’t matter how much they navigate around, nothing from their cart will be lost!

So, in short, I decided it’s probably better to implement Redux now while the codebase for our site isn’t too large.

Implementing Redux for Shopify Buy SDK With Bare Minimum Boilerplate

Install NPM packages redux and react-redux:

npm install --save redux react-redux

In index.js , import Provider from react-redux and your store from ./store:

import { Provider } from 'react-redux';

import store from './store';

Wrap the er> component with the p assed store aroun d your&l t;App>;in index.jsto hook up your App to your Redux store:

ReactDOM.render(

   

     

   

,

document.getElementById('root')

);

(Note that I also have a er>, but that’s in a different post about how I applied internationalization and localization to dynamically render the content on Siren Apparel’s site. A different story for a different day.)

Now of course we haven’t made a ./store.js file yet. Create your store in store.jsin the src/ root and put this in it:

import {createStore} from 'redux';

import reducer from './reducers/cart';export default createStore(reducer);

Create your reducers file in src/reducers/cart.js and paste this code:

// initial state

const initState = {

 isCartOpen: false,

 checkout: { lineItems: [] },

 products: [],

 shop: {}

}// actions

const CLIENT_CREATED = 'CLIENT_CREATED'

const PRODUCTS_FOUND = 'PRODUCTS_FOUND'

const CHECKOUT_FOUND = 'CHECKOUT_FOUND'

const SHOP_FOUND = 'SHOP_FOUND'

const ADD_VARIANT_TO_CART = 'ADD_VARIANT_TO_CART'

const UPDATE_QUANTITY_IN_CART = 'UPDATE_QUANTITY_IN_CART'

const REMOVE_LINE_ITEM_IN_CART = 'REMOVE_LINE_ITEM_IN_CART'

const OPEN_CART = 'OPEN_CART'

const CLOSE_CART = 'CLOSE_CART'// reducers

export default (state = initState, action) => {

 switch (action.type) {

   case CLIENT_CREATED:

     return {...state, client: action.payload}

   case PRODUCTS_FOUND:

     return {...state, products: action.payload}

   case CHECKOUT_FOUND:

     return {...state, checkout: action.payload}

   case SHOP_FOUND:

     return {...state, shop: action.payload}

   case ADD_VARIANT_TO_CART:

     return {...state, isCartOpen: action.payload.isCartOpen, checkout: action.payload.checkout}

   case UPDATE_QUANTITY_IN_CART:

     return {...state, checkout: action.payload.checkout}

   case REMOVE_LINE_ITEM_IN_CART:

     return {...state, checkout: action.payload.checkout}

   case OPEN_CART:

     return {...state, isCartOpen: true}

   case CLOSE_CART:

     return {...state, isCartOpen: false}

   default:

     return state

 }

}

Don’t worry, I’m not going to just post this big reducer and not discuss what is going on; we’ll get to each event! There are a few things to note here.

We take the initial state from what the state is written as in the Shopify GitHub example and put it in our initState, namely the following four parts of state:

isCartOpen: false,

checkout: { lineItems: [] },

products: [],

shop: {}

However, in my implementation, I also create a client part of the state. I call the createClient() function once and then immediately set it in the Redux state in index.js . So let’s head into index.js:

Back to index.js

const client = Client.buildClient({

 storefrontAccessToken: 'your-shopify-token',

 domain: 'your-shopify-url.myshopify.com'

});

store.dispatch({type: 'CLIENT_CREATED', payload: client});

In the Shopify buy SDK example, there are a few async calls to get information about the products and store information in React’s componentWillMount() function. That example code looks like this:

componentWillMount() {

   this.props.client.checkout.create().then((res) => {

     this.setState({

       checkout: res,

     });

   });this.props.client.product.fetchAll().then((res) => {

     this.setState({

       products: res,

     });

   });this.props.client.shop.fetchInfo().then((res) => {

     this.setState({

       shop: res,

     });

   });

 }

I opted to do that instead as far upstream of a site load as possible, directly in index.js. Then, I issued a corresponding event when each part of the response has been received:

// buildClient() is synchronous, so we can call all these after!

client.product.fetchAll().then((res) => {

 store.dispatch({type: 'PRODUCTS_FOUND', payload: res});

});

client.checkout.create().then((res) => {

 store.dispatch({type: 'CHECKOUT_FOUND', payload: res});

});

client.shop.fetchInfo().then((res) => {

 store.dispatch({type: 'SHOP_FOUND', payload: res});

});

By now the reducer is created, and the initialization of the Shopify API client is complete all for index.js.

Back to App.js

Now in App.js, wire up Redux’s store to the App state:

import { connect } from 'react-redux';

and don’t forget to import the store as well:

import store from './store';

At the bottom where export default App should be, modify it to this:

export default connect((state) => state)(App);

This connects the Redux state to the App component.

Now in the render() function we are able to access the Redux’s state with Redux’s getState() (as apposed to using vanilla react’s this.state):

render() {

   ...    

   const state = store.getState();

}

Finally: the Event Handlers (We’re Still in App.js)

From above, you know that there are only three event handlers that we need in App.js, because the cart uses only three: updateQuantityInCart, removeLineItemInCart, and handleCartClose. The original cart event handlers from the example GitHub repository, which used local component state looked like this:

updateQuantityInCart(lineItemId, quantity) {

 const checkoutId = this.state.checkout.id

 const lineItemsToUpdate = [{id: lineItemId, quantity: parseInt(quantity, 10)}]return this.props.client.checkout.updateLineItems(checkoutId, lineItemsToUpdate).then(res => {

   this.setState({

     checkout: res,

   });

 });

}removeLineItemInCart(lineItemId) {

 const checkoutId = this.state.checkout.idreturn this.props.client.checkout.removeLineItems(checkoutId, [lineItemId]).then(res => {

   this.setState({

     checkout: res,

   });

 });

}handleCartClose() {

 this.setState({

   isCartOpen: false,

 });

}

We can refactor them to dispatch events to the Redux store as follows:

updateQuantityInCart(lineItemId, quantity) {

   const state = store.getState(); // state from redux store

   const checkoutId = state.checkout.id

   const lineItemsToUpdate = [{id: lineItemId, quantity: parseInt(quantity, 10)}]

   state.client.checkout.updateLineItems(checkoutId, lineItemsToUpdate).then(res => {

     store.dispatch({type: 'UPDATE_QUANTITY_IN_CART', payload: {checkout: res}});

   });

}

removeLineItemInCart(lineItemId) {

   const state = store.getState(); // state from redux store

   const checkoutId = state.checkout.id

   state.client.checkout.removeLineItems(checkoutId, [lineItemId]).then(res => {

     store.dispatch({type: 'REMOVE_LINE_ITEM_IN_CART', payload: {checkout: res}});

   });

}

handleCartClose() {

   store.dispatch({type: 'CLOSE_CART'});

}

handleCartOpen() {

   store.dispatch({type: 'OPEN_CART'});

}

If you were following along, I already mentioned that I added my own handleCartOpen function, because I pass that function down as a prop to my v/> component, so a user is able to open and close the cart from a link in the nav. At a future time, I could move that function to the Nav itself instead of passing it as a prop, since of course the Redux store will also be available there!

Finally Add that Component!

So, you’ve got a basic store maybe with some simple href’s that link to the corresponding product on your Shopify store? Ha! Get rid of those, and replace them with your brand spankin’ new s/> component!

First, import the component into wherever your store markup should be (remember, in my code base I’ve put the shopify example components in a folder called shopify/)

This will be wherever your products currently are. (In the boilerplate repository I made, I put this in the GenericProductsPage component, to signal that this code could be applied to any page that has a products section):

import Products from './shopify/Products';

Now finally, that past 15–20 minutes of redux boilerplate code edits pays off: we can grab the products part of our state — not by way of vanilla React state passed down over and over again through props — but through grabbing by way of Redux state, in a neat one liner const state = store.getState();:

render () {

   const state = store.getState(); // state from redux store

   let oProducts =

     products={state.products}

     client={state.client}

     addVariantToCart={this.addVariantToCart}

   />;

Don’t forget to drop the component itself into where it should go in your render() function. For me, that location was buried in Bootstrap style classes and HTML:

...

       

{/*/.row*/}

{/*/.service-content-one*/}

...

Finally, we will need a single event function addVariantToCart for the cart to work with this products component. Again, for reference, here is the original, vanilla React local state version of addVariantToCart(again, from the shopify example repository):

addVariantToCart(variantId, quantity){

 this.setState({

   isCartOpen: true,

 });const lineItemsToAdd = [{variantId, quantity: parseInt(quantity, 10)}]

 const checkoutId = this.state.checkout.idreturn this.props.client.checkout.addLineItems(checkoutId, lineItemsToAdd).then(res => {

   this.setState({

     checkout: res,

   });

 });

}

and the new, Redux-friendly store.dispatch() version:

addVariantToCart(variantId, quantity) {

   const state = store.getState(); // state from redux store

   const lineItemsToAdd = [{variantId, quantity: parseInt(quantity, 10)}]

   const checkoutId = state.checkout.id

   state.client.checkout.addLineItems(checkoutId, lineItemsToAdd).then(res => {

     store.dispatch({type: 'ADD_VARIANT_TO_CART', payload: {isCartOpen: true, checkout: res}});

   });

}

which is of course the one we will use. ?

Don’t forget to bind it in the constructor:

this.addVariantToCart = this.addVariantToCart.bind(this);

Also, you’ll need to connect this component to the store like you did App.js , and import the store:

import { connect } from 'react-redux'

import store from '../store';

at the top, and (assuming the component where you put the Shopify Product component name is GenericProductPage:

export default connect((state) => state)(GenericProductsPage);

at the bottom.

Great! Now, no matter how deeply buried in components, or wherever your products component is declared, it can communicate with the cart’s state!

Final BONUS Example: Cart in Your Header or Nav

If you want to have a ‘Cart’ button in your header / nav, add this button in your Nav component’s render function (again, an example from my current site, which has Bootstrap styles — a very simple version is in the boilerplate example:

   Cart

   

where handleCartOpen is a new handler method you’ll have to add to App.js:

constructor() {

 super();

 ...

 this.handleCartOpen = this.handleCartOpen.bind(this);

 ...

}

in the constructor. Then when you are referencing your Nav component in App.js (or wherever you place your Nav) you pass the function handler:

This could also be refactored to an event in Redux, but since it was only one child down, I did it the vanilla React way.

Styling Component(s)

I relied on Shopify’s CSS file, app.css, located in the shared/ folder in the storefront-api-example repository (you can’t miss it, it’s the only file in shared/ )!

Make sure to copy that into your styles/ folder or wherever it needs to be and include it in your index.js file. In my index.js it looks like this:

import './styles/shopify.css';

Since I renamed the app.css which was in the Shopify example repository to shopify.css , and put it folder styles. This convention is also used in the boilerplate repository code.

From here it’s pretty easy to identify where exactly in shopify.css the default bright blue color for the buttons is defined, and so on. I’m going to save detailed CSS customization for you to handle. ?

But who knows, maybe I’ll post on that eventually — but I find the styles from Shopify pretty good and easy enough to modify.

Takeaways

In my opinion, this is a perfect (non-todo list ?) use of Redux. Redux cleanly organizes the event functions and state of the Shopify cart and makes it easy to access the cart’s state from any other component. This is much easier to maintain than passing pieces of state to children and using multiple event handlers to pass events back up to parent functions all over a React app.

As shown as an example in the tutorial, the cart’s state is accessed easily in the Nav component and the shop section of the front page. I’ll also be able to easily add it to a sort of ‘featured’ product section as well, once Siren Apparel is ready for that.

Find the Code

A boilerplate repository of this implementation can be found here. It is a near blank create-react-app app, but with all the changes of this tutorial implemented in index.js and App.js , as well as a super basic GenericStorePage and Nav components.

I built the code on the repo while re-reading and updating my own tutorial here, to make sure this tutorial makes sense!

Because I am crazy ?, Siren Apparel’s website is all open-sourced. So if you want to fool around with my implementation, check out the repository!

I hope you enjoyed this tutorial! If anything isn’t clear or just plain not working, let me know! I’ll try to assist you!

Thanks to Lisa Catalano at CSS-Snippets for the simple Nav example which I used in the boilerplate repository!

Cheers! ?

Chris