Un tutorial dettagliato: come utilizzare l'API Storefront di Shopify con React e Redux

E-commerce per tutti! (... siti web, ovvero )

Scritto da Chris Agosto 2018, aggiornato a novembre 2018

Per gentile concessione di Negative Space su pexels.com

Contesto e motivazione

Quindi la motivazione qui era piuttosto semplice. Volevo che i visitatori del mio sito potessero navigare, cercare e selezionare i prodotti direttamente sul mio dominio personalizzato senza dover visitare il nostro sito Shopify.

La motivazione secondaria è che preferirei avere la mia base di codice per un sito Web piuttosto che utilizzare uno dei modelli di fabbrica di Shopify. Squadra Shopify senza offesa! I modelli sono moderni e puliti, ma sono piuttosto semplici. Sono sicuro che quei modelli sono fortemente personalizzabili, ma non è uno stack che conosco al momento.

Quindi questo è il migliore dei due mondi: il mio sito React personalizzato (già costruito e online ), con l'API e il processo di checkout aggiunti di Shopify!

Alla fine di questo tutorial, sarai in grado di aggiungere i tuoi prodotti Shopify su qualsiasi pagina del tuo sito. L'unica parte del processo di acquisto che si verificherà su Shopify è quando l'utente fa clic su "Acquista".

Per questo tutorial ho creato un repository di boilerplate vuoto.

La motivazione specifica per scrivere qui su Medium era semplicemente che non riuscivo a trovare un tutorial su questo processo da solo, quindi ho deciso di crearne uno!

Sono uno sviluppatore professionista da 4 anni e programmando per 7. Ho lavorato in stack tecnologici dalla vecchia scuola Fortran e Perl, a React, Javascript, Python e Node.

Siren Apparel è una delle mie società secondarie di progetti / startup / produttori che gestisco da 5 anni e finora abbiamo donato a 5 diversi dipartimenti di polizia e vigili del fuoco!

Iniziamo finalmente con questo tutorial.

API Storefront di Shopify

Le persone meravigliose di Shopify hanno messo insieme l'API Storefront. Con l'API Storefront, puoi creare componenti React per aggiungere immagini di prodotti, variazioni di prodotti, dimensioni dei prodotti, un carrello e pulsanti "aggiungi al carrello" e "checkout" nel tuo sito non Shopify.

* Nota che questo tutorial NON riguarda Shopify Polaris, che viene utilizzato per creare componenti in React per la gestione del negozio Shopify stesso.

Per iniziare: Repository-reazioni-js-buy

Dai un'occhiata a questo esempio di React creato dal team di Shopify. La maggior parte del codice in questo tutorial proviene da quel repository.

... hai dato un'occhiata? Buono!

Ora entreremo nel codice! Vai alla cartella principale del tuo sito React e installa il modulo shopify-buy tramite il terminale:

cd my-awesome-reagire-progetto /
npm install - salva shopify-buy

(o filato aggiungi shopify-buy se preferisci il filato)

Quindi, nel tuo frontend index.js, (NON App.js!) Dovrai importare il Client dall'SDK JS Buy:

importare il client da "shopify-buy";

Quindi aggiungere il seguente oggetto di configurazione sopra la chiamata ReactDOM.render ():

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

Questo è tutto per index.js per ora - ci torneremo presto.

Ora aggiungeremo tutti i componenti necessari per un'esperienza di acquisto e pagamento senza intoppi. Copia tutti i componenti dal repository reazioni-js-buy:

Cart.js

LineItem.js

Product.js

Products.js

VariantSelector.js

Incolleremo questi componenti nella cartella acomponents / shopify / nella cartella src /. Se lo desideri, puoi inserire questi file dei componenti in qualsiasi altra parte della cartella src /. Il resto del tutorial presuppone che tu li abbia inseriti in componenti / shopify /.

Modifica App.js

App.js avrà bisogno di ampie modifiche. Innanzitutto, importa quel componente del carrello che hai appena copiato nel tuo progetto:

importare carrello da "./components/shopify/Cart";

Se il componente App.js era senza stato, come il mio, dovresti essere sicuro di copiare l'intera funzione di costruzione ():

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

Se hai già uno stato, copia solo quelle linee di rilegatura. Queste tre righe sono funzioni del gestore eventi di cui il carrello Shopify deve funzionare correttamente.

"Ma per quanto riguarda lo stato per il carrello !?"

Si può chiedere; o:

"Che ne dici di definire quei gestori di eventi per il carrello !?"

Anzi, sta arrivando, ma non ancora!

È quindi possibile aggiungere il componente alla fine della funzione render (), prima del div finale.

Secondo me, il carrello dovrebbe essere accessibile ovunque nella tua app. Penso che abbia senso, quindi, inserire il componente nel componente radice della tua app - in altre parole, App.js:

ritorno (
... );

Ancora una volta, non ho ancora incluso alcun codice nei gestori di eventi per il carrello. Inoltre, non ho risolto la mancanza di componenti statali per il carrello in App.js.

C'è una buona ragione per questo.

Circa a metà di questo progetto, mi sono reso conto che il mio componente di prodotti non era ovviamente nel mio file App.js.

Invece, è stato sepolto circa tre componenti di bambini giù.

Quindi, invece di passare i prodotti di tre livelli ai bambini, e quindi di eseguire il backup dei gestori fino al ...

Ho deciso di usare ...

Redux !!!

Ugh! Lo so, lo so, Redux, pur non essendo molto difficile, è un dolore in% * $! collegarsi inizialmente con tutta la piastra della caldaia richiesta. Ma, se sei uno sviluppatore che lavora in un negozio di e-commerce o un proprietario di un negozio di e-commerce, pensaci in questo modo: Redux ti consentirà di accedere allo stato del carrello da qualsiasi componente o pagina del nostro sito Web o webapp.

Questa capacità sarà essenziale man mano che Siren Apparel si espande e sviluppiamo più prodotti. Man mano che creiamo più prodotti, creerò una pagina di negozio dedicata separata con tutti i prodotti, lasciando solo una manciata di prodotti in primo piano nella home page.

La possibilità di accedere al carrello è essenziale se un utente cerca un po 'in giro, legge alcune storie o informazioni su Siren Apparel e quindi decide di effettuare il checkout. Non importa quanto navigano, nulla del loro carrello andrà perso!

Quindi, in breve, ho deciso che probabilmente è meglio implementare Redux ora mentre la base di codice per il nostro sito non è troppo grande.

Implementazione di Redux per Shopify Acquista SDK con caldaia minima nuda

Installa i pacchetti NPM redux e reazioni-redux:

npm install --save redux reagire-redux

In index.js, importare il provider da reagire-redux e il tuo negozio da ./store:

importare {Provider} da 'reply-redux';
importare store da './store';

Avvolgi il componente con l'archivio passato attorno al tuo in index.js per collegare l'app al tuo negozio Redux:

ReactDOM.render (

    
      
    
 ,
document.getElementById ( 'radice')
);

(Nota che ho anche un , ma è in un post diverso su come ho applicato l'internazionalizzazione e la localizzazione per rendere dinamicamente i contenuti sul sito di Siren Apparel. Una storia diversa per un giorno diverso.)

Ora ovviamente non abbiamo ancora creato un file ./store.js. Crea il tuo negozio in store.jsin src / root e inseriscilo:

importare {createStore} da 'redux';
riduttore di importazione da "./reducers/cart";
export default createStore (riduttore);

Crea il tuo riduttore in src / riduzione / cart.js e incolla questo codice:

// stato iniziale
const initState = {
  isCartOpen: false,
  checkout: {lineItems: []},
  prodotti: [],
  negozio: {}
}
// Azioni
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'
// riduttori
export default (state = initState, action) => {
  switch (action.type) {
    case CLIENT_CREATED:
      return {... state, client: action.payload}
    case PRODUCTS_FOUND:
      return {... state, products: action.payload}
    caso CHECKOUT_FOUND:
      return {... state, checkout: action.payload}
    case SHOP_FOUND:
      return {... state, shop: action.payload}
    caso ADD_VARIANT_TO_CART:
      return {... state, isCartOpen: action.payload.isCartOpen, checkout: action.payload.checkout}
    caso UPDATE_QUANTITY_IN_CART:
      return {... state, checkout: action.payload.checkout}
    caso REMOVE_LINE_ITEM_IN_CART:
      return {... state, checkout: action.payload.checkout}
    caso OPEN_CART:
      return {... state, isCartOpen: true}
    caso CLOSE_CART:
      return {... state, isCartOpen: false}
    predefinito:
      stato di ritorno
  }
}

Non preoccuparti, non pubblicherò questo grande riduttore e non discuterò di quello che sta succedendo; arriveremo ad ogni evento! Ci sono alcune cose da notare qui.

Prendiamo lo stato iniziale da ciò che è scritto come nell'esempio di Shopify GitHub e lo inseriamo nel nostro initState, vale a dire le seguenti quattro parti di stato:

isCartOpen: false,
checkout: {lineItems: []},
prodotti: [],
negozio: {}

Tuttavia, nella mia implementazione, creo anche una parte client dello stato. Chiamo la funzione createClient () una volta e poi la imposto immediatamente nello stato Redux in index.js. Quindi andiamo in index.js:

Torna a index.js

const client = Client.buildClient ({
  storefrontAccessToken: 'your-shopify-token',
  dominio: "your-shopify-url.myshopify.com"
});
store.dispatch ({tipo: 'CLIENT_CREATED', payload: client});

Nell'esempio di acquisto dell'SDK di Shopify, ci sono alcune chiamate asincrone per ottenere informazioni sui prodotti e archiviare informazioni nella funzione componentWillMount () di React. Questo codice di esempio è simile al seguente:

componentWillMount () {
    this.props.client.checkout.create (). then ((res) => {
      this.setState ({
        checkout: res,
      });
    });
this.props.client.product.fetchAll (). then ((res) => {
      this.setState ({
        prodotti: res,
      });
    });
this.props.client.shop.fetchInfo (). then ((res) => {
      this.setState ({
        negozio: res,
      });
    });
  }

Ho optato per farlo invece il più a monte di un caricamento del sito possibile, direttamente in index.js. Quindi, ho emesso un evento corrispondente quando ogni parte della risposta è stata ricevuta:

// buildClient () è sincrono, quindi possiamo chiamarli tutti dopo!
client.product.fetchAll (). then ((res) => {
  store.dispatch ({tipo: 'PRODUCTS_FOUND', payload: res});
});
client.checkout.create (). then ((res) => {
  store.dispatch ({tipo: 'CHECKOUT_FOUND', payload: res});
});
client.shop.fetchInfo (). then ((res) => {
  store.dispatch ({tipo: 'SHOP_FOUND', payload: res});
});

Ormai viene creato il riduttore e l'inizializzazione del client API di Shopify è completa per index.js.

Torna ad App.js

Ora in App.js, collega lo store di Redux allo stato App:

importare {connect} da 'reply-redux';

e non dimenticare di importare anche il negozio:

importare store da './store';

Nella parte inferiore dove dovrebbe trovarsi l'app predefinita di esportazione, modificala in questo modo:

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

Ciò collega lo stato Redux al componente App.

Ora nella funzione render () siamo in grado di accedere allo stato di Redux con getState () di Redux (come acconsentito all'utilizzo di vanilla reazioni è this.state):

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

Finalmente: i gestori di eventi (Siamo ancora in App.js)

Da sopra, sai che ci sono solo tre gestori di eventi di cui abbiamo bisogno in App.js, perché il carrello ne utilizza solo tre: updateQuantityInCart, removeLineItemInCart e handleCartClose. I gestori di eventi carrello originali dal repository GitHub di esempio, che utilizzava lo stato del componente locale, apparivano così:

updateQuantityInCart (lineItemId, quantità) {
  const checkoutId = this.state.checkout.id
  const lineItemsToUpdate = [{id: lineItemId, quantità: parseInt (quantità, 10)}]
return this.props.client.checkout.updateLineItems (checkoutId, lineItemsToUpdate) .then (res => {
    this.setState ({
      checkout: res,
    });
  });
}
removeLineItemInCart (lineItemId) {
  const checkoutId = this.state.checkout.id
restituire this.props.client.checkout.removeLineItems (checkoutId, [lineItemId]). then (res => {
    this.setState ({
      checkout: res,
    });
  });
}
handleCartClose () {
  this.setState ({
    isCartOpen: false,
  });
}

Possiamo riformattarli per inviare gli eventi al negozio Redux come segue:

updateQuantityInCart (lineItemId, quantità) {
    const state = store.getState (); // indica dal redux store
    const checkoutId = state.checkout.id
    const lineItemsToUpdate = [{id: lineItemId, quantità: parseInt (quantità, 10)}]
    state.client.checkout.updateLineItems (checkoutId, lineItemsToUpdate) .then (res => {
      store.dispatch ({tipo: 'UPDATE_QUANTITY_IN_CART', payload: {checkout: res}});
    });
}
removeLineItemInCart (lineItemId) {
    const state = store.getState (); // indica dal redux store
    const checkoutId = state.checkout.id
    state.client.checkout.removeLineItems (checkoutId, [lineItemId]). then (res => {
      store.dispatch ({tipo: 'REMOVE_LINE_ITEM_IN_CART', payload: {checkout: res}});
    });
}
handleCartClose () {
    store.dispatch ({tipo: 'CLOSE_CART'});
}
handleCartOpen () {
    store.dispatch ({tipo: 'OPEN_CART'});
}

Se stavi seguendo, ho già detto che ho aggiunto la mia funzione handleCartOpen, perché ho passato quella funzione come prop al mio componente