En detaljeret tutorial: hvordan man bruger Shopifys Storefront API med React og Redux

E-handel for alle! (… Websteder, det er )

Skrevet af Chris august 2018, opdateret november 2018

Med tillæg til negativ plads på pexels.com

Baggrund og motivation

Så motivationen her var temmelig enkel. Jeg ønskede, at mine besøgende skulle kunne gennemse, søge og vælge produkter direkte på mit brugerdefinerede domæne uden at skulle gå til vores Shopify-site.

Den sekundære motivation er, at jeg meget hellere ville have min egen kodebase til et websted end at bruge en af ​​Shopifys fabriksskabeloner. Ingen overtrædelser Shopify team! Skabelonerne er moderne og rene, men de er ret basale. Jeg er sikker på, at disse skabeloner er meget tilpasselige, men det er ikke en stak, jeg kender i øjeblikket.

Så dette er det bedste fra begge verdener - mit brugerdefinerede React-websted (allerede bygget og online ), med den tilføjede API og checkoutproces hos Shopify!

Ved afslutningen af ​​denne tutorial vil du være i stand til at tilføje dine Shopify-produkter på enhver side på dit websted. Den eneste del af shoppingprocessen, der finder sted på Shopify, er, når brugeren klikker på 'Checkout'.

Jeg har også oprettet et tomt kedelplade til denne tutorial.

Motivationen specifikt til at skrive her på Medium var simpelthen, at jeg ikke kunne finde en tutorial til denne proces selv - så jeg besluttede at lave en!

Jeg har været en professionel udvikler i 4 år nu og programmeret i 7. Jeg har arbejdet i tech-stacks fra old-school Fortran og Perl til React, Javascript, Python og Node.

Siren Apparel er et af mine sideprojekter / opstart / producenterfirmaer, som jeg har kørt i 5 år nu, og vi har doneret til 5 forskellige politi- og brandvæsener indtil videre!

Lad os endelig komme i gang med denne tutorial.

Shopifys Storefront API

De vidunderlige folk hos Shopify har sammensat Storefront API. Med Storefront API kan du oprette React-komponenter for at tilføje produktbilleder, produktvariationer, produktstørrelser, en indkøbsvogn og 'tilføj til indkøbskurv' og 'checkout'-knapper til dit eget, ikke-Shopify-site.

* Bemærk, at denne selvstudie IKKE handler om Shopify Polaris, som bruges til at oprette komponenter i React for Shopify-butiksstyring i sig selv.

Kom godt i gang: react-js-buy Repository

Se på dette React-eksempel bygget af Shopify-teamet. Det meste af koden i denne tutorial kommer fra det depot.

… Kiggede du på? Godt!

Nu vil vi hoppe ind i kode! Gå til dit React-websteds rodmappe, og installer shopify-buy-modulet via terminalen:

cd my-awesome-react-project /
npm installation - gem shopify-buy

(eller garn tilføj shopify-buy, hvis du foretrækker garn)

Derefter skal du i din frontend index.js (IKKE App.js!) Importere klient fra JS Buy SDK:

importer klient fra 'shopify-buy';

Tilføj derefter følgende konfigurationsobjekt over opkaldet ReactDOM.render ():

const client = Client.buildClient ({
    storefrontAccessToken: 'din-adgang-token',
    domæne: 'din-shopify-url.myshopify.com'
});

Det er det for index.js for nu - vi kommer snart tilbage til det.

Nu vil vi tilføje alle de komponenter, der er nødvendige for en glat shopping- og check-oplevelse. Kopier alle komponenter fra react-js-buy-arkivet:

Cart.js

LineItem.js

Product.js

Products.js

VariantSelector.js

Vi indsætter disse komponenter i acomponents / shopify / folder i din src / folder. Du kan placere disse komponentfiler andre steder i src / mappen, hvis du ønsker det. Resten af ​​selvstudiet antager, at du har lagt dem i komponenter / shopify /.

Ændring af App.js

App.js har brug for omfattende ændringer. Først skal du importere den vognkomponent, du lige har kopieret til dit eget projekt:

importer indkøbsvogn fra './components/shopify/Cart';

Hvis din App.js-komponent var statsløs som min, skal du være sikker på at kopiere hele denne konstruktør () -funktion:

konstruktør () {
    super();
    this.updateQuantityInCart = this.updateQuantityInCart.bind (dette);
    this.removeLineItemInCart = this.removeLineItemInCart.bind (dette);
    this.handleCartClose = this.handleCartClose.bind (dette);
}

Hvis du allerede har status, skal du kun kopiere disse bindelinjer. Disse tre linjer er begivenhedshåndteringsfunktioner, som Shopify-indkøbskurven skal fungere korrekt.

”Men hvad med tilstanden til vognen !?”

Du kan spørge; eller:

"Hvad med at definere disse begivenhedshåndterere til indkøbskurven !?"

Det kommer faktisk, men endnu ikke!

Du kan derefter tilføje -komponenten til bunden af ​​din render () -funktion, inden den afsluttende div.

Efter min mening skal indkøbskurven være tilgængelig overalt i din app. Jeg tror, ​​at det giver mening at placere -komponenten i rodkomponenten i din app - med andre ord App.js:

Vend tilbage (
... );

Igen har jeg endnu ikke medtaget nogen kode på begivenhedshåndterere til indkøbskurven. Derudover behandlede jeg ikke manglen på tilstandskomponenter til indkøbskurven i App.js.

Der er god grund til dette.

Cirka halvvejs gennem dette projekt indså jeg, at min produktkomponent naturligvis ikke var i min App.js-fil.

I stedet blev det begravet omkring tre børnekomponenter nede.

Så i stedet for at videregive produkter tre niveauer ned til børn, og derefter fungere håndterere helt tilbage op ...

Jeg besluttede at bruge ...

Redux !!!

Ugh! Jeg ved, jeg ved, Redux, selv om det ikke er meget vanskeligt, er en smerte i% * $! til oprindelig at trække op med al den nødvendige kedelplade. Men hvis du er en udvikler, der arbejder på en e-handel butik eller en e-handel butiksejer, så tænk på det på denne måde: Redux giver dig mulighed for at få adgang til vognens tilstand fra enhver komponent eller side på vores websted eller webapp.

Denne evne vil være vigtig, når Siren Apparel udvides, og vi udvikler flere produkter. Efterhånden som vi opretter flere produkter, opretter jeg en separat dedikeret butiksside med alle produkter, mens jeg kun lader en håndfuld featured produkter på hjemmesiden.

Muligheden for at få adgang til indkøbskurven er afgørende, hvis en bruger handler lidt rundt, læser nogle historier eller info om Siren Apparel og derefter beslutter at kassen. Det betyder ikke noget, hvor meget de navigerer rundt, intet fra deres indkøbsvogn vil gå tabt!

Så kort sagt besluttede jeg, at det sandsynligvis er bedre at implementere Redux nu, mens kodebasen for vores websted ikke er for stor.

Implementering af Redux til Shopify Køb SDK med bare minimum kedelplade

Installer NPM-pakker redux og react-redux:

npm installation - gem redux reaktionsreduktion

I index.js skal du importere udbyder fra react-redux og din butik fra ./store:

import {Provider} fra 'react-redux';
import butik fra './store';

Pakk -komponenten med den passerede butik omkring din i indeks.jst tilslut din app til din Redux-butik:

ReactDOM.render (

    
      
    
 ,
document.getElementById ( 'root')
);

(Bemærk, at jeg også har en , men det står i et andet indlæg om, hvordan jeg anvendte internationalisering og lokalisering for dynamisk at gengive indholdet på Siren Apparels websted. En anden historie for en anden dag.)

Nu har vi selvfølgelig ikke lavet en ./store.js-fil endnu. Opret din butik i store.js i src / root og læg den i den:

import {createStore} fra 'redux';
import reducer fra './reducers/cart';
eksport standard createStore (reducer);

Opret din reduceringsfil i src / reducers / cart.js og indsæt denne kode:

// starttilstand
const initState = {
  isCartOpen: falsk,
  kassen: {lineItems: []},
  Produkter: [],
  butik: {}
}
// handlinger
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'
// reduktionsmaskiner
eksportstandard (tilstand = initState, handling) => {
  switch (action.type) {
    sag CLIENT_CREATED:
      returner {... state, client: action.payload}
    sag PRODUCTS_FOUND:
      returner {... tilstand, produkter: action.payload}
    sag CHECKOUT_FOUND:
      retur {... tilstand, kasse: action.payload}
    sag SHOP_FOUND:
      returner {... state, shop: action.payload}
    sag ADD_VARIANT_TO_CART:
      return {... state, isCartOpen: action.payload.isCartOpen, checkout: action.payload.checkout}
    sag UPDATE_QUANTITY_IN_CART:
      returner {... tilstand, kasse: action.payload.checkout}
    sag REMOVE_LINE_ITEM_IN_CART:
      returner {... tilstand, kasse: action.payload.checkout}
    sag OPEN_CART:
      return {... state, isCartOpen: true}
    sag CLOSE_CART:
      returner {... state, isCartOpen: false}
    Standard:
      returtilstand
  }
}

Bare rolig, jeg vil ikke bare skrive denne store reducer og ikke diskutere, hvad der foregår; vi kommer til hver begivenhed! Der er et par ting at bemærke her.

Vi tager den oprindelige tilstand fra, hvad staten er skrevet som i Shopify GitHub-eksemplet og lægger den i vores initState, nemlig de følgende fire dele af staten:

isCartOpen: falsk,
kassen: {lineItems: []},
Produkter: [],
butik: {}

Imidlertid opretter jeg også i min implementering en klientdel af staten. Jeg kalder funktionen createClient () én gang og indstiller den derefter straks i Redux-tilstand i index.js. Så lad os gå ind på index.js:

Tilbage til index.js

const client = Client.buildClient ({
  storefrontAccessToken: 'din-shopify-token',
  domæne: 'din-shopify-url.myshopify.com'
});
store.dispatch ({type: 'CLIENT_CREATED', nyttelast: klient});

I Shopify-køb-SDK-eksemplet er der et par async-opkald for at få oplysninger om produkterne og gemme oplysninger i React's componentWillMount () -funktion. Eksempelkoden ser sådan ud:

componentWillMount () {
    this.props.client.checkout.create (). derefter ((res) => {
      this.setState ({
        kasse: res,
      });
    });
this.props.client.product.fetchAll (). derefter ((res) => {
      this.setState ({
        produkter: res,
      });
    });
this.props.client.shop.fetchInfo (). derefter ((res) => {
      this.setState ({
        butik: res,
      });
    });
  }

Jeg valgte at gøre det i stedet så langt opstrøms for en webstedsbelastning som muligt direkte i index.js. Derefter udstedte jeg en tilsvarende begivenhed, når hver del af svaret blev modtaget:

// buildClient () er synkron, så vi kan kalde alle disse efter!
client.product.fetchAll (). derefter ((res) => {
  store.dispatch ({type: 'PRODUCTS_FOUND', nyttelast: res});
});
client.checkout.create (). derefter ((res) => {
  store.dispatch ({type: 'CHECKOUT_FOUND', nyttelast: res});
});
client.shop.fetchInfo (). derefter ((res) => {
  store.dispatch ({type: 'SHOP_FOUND', nyttelast: res});
});

I øjeblikket oprettes reduceringsenheden, og initialiseringen af ​​Shopify API-klienten er fuldført alt for index.js.

Tilbage til App.js

Tilslut nu Redux's butik i App.js til App-tilstand:

import {connect} fra 'react-redux';

og glem ikke at importere butikken også:

import butik fra './store';

I bunden, hvor eksportens standardapp skal være, skal du ændre den til denne:

eksport standardforbindelse ((tilstand) => tilstand) (App);

Dette forbinder Redux-tilstanden til App-komponenten.

Nu i render () -funktionen er vi i stand til at få adgang til Redux's tilstand med Redux's getState () (som apposed til at bruge vaniljreakt's this.state):

render () {
    ...
    const state = store.getState ();
}

Endelig: Event Handlers (Vi er stadig i App.js)

Fra oven ved du, at der kun er tre begivenhedshåndterere, som vi har brug for i App.js, fordi indkøbskurven kun bruger tre: updateQuantityInCart, removeLineItemInCart og handleCartClose. De originale handlere med vognbegivenheder fra eksempelet GitHub-lager, der brugte lokal komponenttilstand, så sådan ud:

updateQuantityInCart (lineItemId, antal) {
  const checkoutId = this.state.checkout.id
  const lineItemsToUpdate = [{id: lineItemId, antal: parseInt (antal, 10)}]
returner this.props.client.checkout.updateLineItems (checkoutId, lineItemsToUpdate) .then (res => {
    this.setState ({
      kasse: res,
    });
  });
}
removeLineItemInCart (lineItemId) {
  const checkoutId = this.state.checkout.id
returner dette.props.client.checkout.removeLineItems (checkoutId, [lineItemId]). derefter (res => {
    this.setState ({
      kasse: res,
    });
  });
}
handleCartClose () {
  this.setState ({
    isCartOpen: falsk,
  });
}

Vi kan refactor dem til at sende begivenheder til Redux-butikken som følger:

updateQuantityInCart (lineItemId, antal) {
    const state = store.getState (); // tilstand fra redux butik
    const checkoutId = state.checkout.id
    const lineItemsToUpdate = [{id: lineItemId, antal: parseInt (antal, 10)}]
    state.client.checkout.updateLineItems (checkoutId, lineItemsToUpdate) .then (res => {
      store.dispatch ({type: 'UPDATE_QUANTITY_IN_CART', nyttelast: {checkout: res}});
    });
}
removeLineItemInCart (lineItemId) {
    const state = store.getState (); // tilstand fra redux butik
    const checkoutId = state.checkout.id
    state.client.checkout.removeLineItems (checkoutId, [lineItemId]). derefter (res => {
      store.dispatch ({type: 'REMOVE_LINE_ITEM_IN_CART', nyttelast: {checkout: res}});
    });
}
handleCartClose () {
    store.dispatch ({type: 'CLOSE_CART'});
}
handleCartOpen () {
    store.dispatch ({type: 'OPEN_CART'});
}

Hvis du fulgte med, nævnte jeg allerede, at jeg tilføjede min egen handleCartOpen-funktion, fordi jeg videregiver denne funktion som en prop til min