Un tutorial detallado: cómo usar Shopify’s Storefront API con React y Redux

¡Comercio electrónico para todos! (... sitios web, eso es )

Escrito por Chris agosto de 2018, actualizado en noviembre de 2018

Cortesía de Negative Space en pexels.com

Antecedentes y Motivación

Entonces la motivación aquí fue bastante simple. Quería que los visitantes de mi sitio pudieran navegar, buscar y seleccionar productos directamente en mi dominio personalizado sin tener que ir a nuestro sitio Shopify.

La motivación secundaria es que prefiero tener mi propia base de código para un sitio web que usar una de las plantillas de fábrica de Shopify. Sin ofender al equipo de Shopify! Las plantillas son modernas y limpias, pero son bastante básicas. Estoy seguro de que esas plantillas son muy personalizables, pero no es una pila que conozco en este momento.

Así que este es el mejor de los dos mundos: ¡mi sitio personalizado React (ya creado y en línea ), con el API agregado y el proceso de pago de Shopify!

Al final de este tutorial, podrá agregar sus productos Shopify en cualquier página de su sitio. La única parte del proceso de compra que ocurrirá en Shopify es cuando el usuario hace clic en "Pagar".

También he creado un repositorio vacío para este tutorial.

La motivación específica para escribir aquí en Medium era simplemente que no podía encontrar un tutorial sobre este proceso, ¡así que decidí hacer uno!

He sido desarrollador profesional durante 4 años y programador durante 7. He trabajado en pilas tecnológicas desde Fortran y Perl de la vieja escuela hasta React, Javascript, Python y Node.

Siren Apparel es una de mis compañías de proyecto / startup / fabricante paralelas que he dirigido durante 5 años, ¡y hasta ahora hemos donado a 5 departamentos de policía y bomberos diferentes!

Comencemos finalmente con este tutorial.

API de escaparate de Shopify

La maravillosa gente de Shopify ha creado la API de Storefront. Con la API de Storefront, puede crear componentes React para agregar imágenes de productos, variaciones de productos, tamaños de productos, un carrito y botones "agregar al carrito" y "finalizar la compra" en su propio sitio que no pertenece a Shopify.

* Tenga en cuenta que este tutorial NO trata sobre Shopify Polaris, que se utiliza para crear componentes en React para la administración de la tienda Shopify.

Primeros pasos: repositorio react-js-buy

Echa un vistazo a este ejemplo de React creado por el equipo de Shopify. La mayor parte del código en este tutorial proviene de ese repositorio.

... ¿Le echaste un vistazo? ¡Bueno!

¡Ahora vamos a saltar directamente al código! Dirígete a la carpeta raíz de tu sitio React e instala el módulo shopify-buy a través del terminal:

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

(o hilo agregue shopify-buy si prefiere hilo)

Luego, en su interfaz index.js, (¡NO App.js!) Deberá importar el Cliente desde el SDK de compra de JS:

Importar cliente desde 'shopify-buy';

Luego agregue el siguiente objeto de configuración sobre la llamada ReactDOM.render ():

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

Eso es todo por index.js por ahora, volveremos pronto.

Ahora vamos a agregar todos los componentes necesarios para una experiencia de compra y pago sin problemas. Copie todos los componentes del repositorio react-js-buy:

Cart.js

LineItem.js

Product.js

Products.js

VariantSelector.js

Pegaremos estos componentes en componentes / shopify / folder en su src / folder. Puede colocar estos archivos componentes en cualquier otro lugar de la carpeta src /, si lo desea. El resto del tutorial asume que los ha puesto en componentes / shopify /.

Modificando App.js

App.js necesitará grandes cambios. Primero, importe ese componente de Carrito que acaba de copiar en su propio proyecto:

importar el carrito desde './components/shopify/Cart';

Si su componente App.js no tenía estado, como el mío, debería estar seguro copiando toda esta función constructor ():

constructor () {
    súper();
    this.updateQuantityInCart = this.updateQuantityInCart.bind (this);
    this.removeLineItemInCart = this.removeLineItemInCart.bind (this);
    this.handleCartClose = this.handleCartClose.bind (this);
}

Si ya tiene estado, copie solo esas líneas de enlace. Esas tres líneas son funciones de controlador de eventos que el carrito de Shopify necesita para funcionar correctamente.

"¿Pero qué pasa con el estado para el carro?"

Tu puedes preguntar; o:

"¿Qué hay de definir esos controladores de eventos para el carrito?"

De hecho, eso viene, ¡pero aún no!

Luego puede agregar el componente al final de su función render (), antes del final div.

En mi opinión, el carrito debe ser accesible desde cualquier lugar de su aplicación. Creo que tiene sentido, entonces, poner el componente en el componente raíz de su aplicación, en otras palabras, App.js:

regreso (
...
);

Nuevamente, todavía no he incluido ningún código en los controladores de eventos para el carrito. Además, no abordé la falta de componentes estatales para el carrito en App.js.

Hay una buena razón para esto.

Aproximadamente a la mitad de este proyecto, me di cuenta de que el componente de mis productos, por supuesto, no estaba en mi archivo App.js.

En cambio, fue enterrado cerca de tres componentes infantiles abajo.

Entonces, en lugar de pasar productos de tres niveles a los niños, y luego volver a manejar los controladores ...

Decidí usar ...

Redux !!!

Ugh! Lo sé, lo sé, Redux, aunque no es muy difícil, ¡es un dolor en% * $! para cablearse inicialmente con toda la placa de caldera requerida. Pero, si usted es un desarrollador que trabaja en una tienda de comercio electrónico o el propietario de una tienda de comercio electrónico, piense de esta manera: Redux le permitirá acceder al estado del carrito desde cualquier componente o página en nuestro sitio web o aplicación web.

Esta capacidad será esencial a medida que Siren Apparel se expanda y desarrollemos más productos. A medida que creamos más productos, crearé una página de tienda dedicada separada con todos los productos, dejando solo un puñado de productos destacados en la página de inicio.

La capacidad de acceder al carrito es esencial si un usuario compra un poco, lee algunas historias o información sobre Siren Apparel y luego decide pagar. ¡No importa cuánto naveguen, no se perderá nada de su carrito!

En resumen, decidí que probablemente sea mejor implementar Redux ahora, mientras que la base de código para nuestro sitio no es demasiado grande.

Implementación de Redux para Shopify Buy SDK con mínimo repetitivo

Instalar paquetes NPM redux y react-redux:

npm install --save redux react-redux

En index.js, importe el proveedor desde react-redux y su tienda desde ./store:

importar {Proveedor} desde 'react-redux';
importar tienda desde './store';

Envuelva el componente con la tienda pasada alrededor de su en index.jsto conecte su aplicación a su tienda Redux:

ReactDOM.render (

    
      
    
 ,
document.getElementById ('root')
);

(Tenga en cuenta que también tengo un , pero eso está en una publicación diferente sobre cómo apliqué la internacionalización y la localización para representar dinámicamente el contenido en el sitio de Siren Apparel. Una historia diferente para un día diferente).

Ahora, por supuesto, todavía no hemos creado un archivo ./store.js. Crea tu tienda en store.jsin el src / root y pon esto en ella:

importar {createStore} desde 'redux';
reductor de importación de './reducers/cart';
exportar createStore predeterminado (reductor);

Cree su archivo de reductores en src / reducers / cart.js y pegue este código:

// estado inicial
const initState = {
  isCartOpen: falso,
  pago: {lineItems: []},
  productos: [],
  tienda: {}
}
// acciones
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'
// reductores
exportación predeterminada (estado = initState, acción) => {
  switch (action.type) {
    caso CLIENT_CREATED:
      return {... estado, cliente: action.payload}
    caso PRODUCTS_FOUND:
      return {... estado, productos: action.payload}
    caso CHECKOUT_FOUND:
      return {... state, checkout: action.payload}
    caso SHOP_FOUND:
      return {... estado, tienda: 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}
    defecto:
      estado de retorno
  }
}

No se preocupe, no voy a publicar este gran reductor y no discutir lo que está sucediendo; ¡llegaremos a cada evento! Hay algunas cosas a tener en cuenta aquí.

Tomamos el estado inicial de cómo está escrito el estado en el ejemplo Shopify GitHub y lo ponemos en nuestro initState, es decir, las siguientes cuatro partes del estado:

isCartOpen: falso,
pago: {lineItems: []},
productos: [],
tienda: {}

Sin embargo, en mi implementación, también creo una parte cliente del estado. Llamo a la función createClient () una vez y luego la configuro inmediatamente en el estado Redux en index.js. Así que vamos a index.js:

Volver a index.js

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

En el ejemplo de Shopify buy SDK, hay algunas llamadas asíncronas para obtener información sobre los productos y almacenar información en la función componentWillMount () de React. Ese código de ejemplo se ve así:

componentWillMount () {
    this.props.client.checkout.create (). then ((res) => {
      this.setState ({
        pago y envío: res,
      });
    });
this.props.client.product.fetchAll (). then ((res) => {
      this.setState ({
        productos: res,
      });
    });
this.props.client.shop.fetchInfo (). then ((res) => {
      this.setState ({
        tienda: res,
      });
    });
  }

Opté por hacer eso lo más arriba posible de una carga del sitio, directamente en index.js. Luego, emití un evento correspondiente cuando se recibió cada parte de la respuesta:

// buildClient () es síncrono, ¡así que podemos llamar a todos estos después!
client.product.fetchAll (). then ((res) => {
  store.dispatch ({type: 'PRODUCTS_FOUND', payload: res});
});
client.checkout.create (). then ((res) => {
  store.dispatch ({tipo: 'CHECKOUT_FOUND', carga útil: res});
});
client.shop.fetchInfo (). then ((res) => {
  store.dispatch ({tipo: 'SHOP_FOUND', carga útil: res});
});

En este momento, se ha creado el reductor y la inicialización del cliente API de Shopify está completa para index.js.

Volver a App.js

Ahora en App.js, conecte la tienda de Redux al estado de la aplicación:

import {connect} de 'react-redux';

y no olvides importar la tienda también:

importar tienda desde './store';

En la parte inferior donde debería estar la aplicación predeterminada de exportación, modifíquela de esta manera:

exportar conexión predeterminada ((estado) => estado) (Aplicación);

Esto conecta el estado de Redux con el componente de la aplicación.

Ahora, en la función render (), podemos acceder al estado de Redux con getState () de Redux (como se supone que usa vanilla react’s this.state):

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

Finalmente: los controladores de eventos (todavía estamos en App.js)

Desde arriba, sabe que solo hay tres controladores de eventos que necesitamos en App.js, porque el carrito usa solo tres: updateQuantityInCart, removeLineItemInCart y handleCartClose. Los controladores de eventos de carrito originales del repositorio de ejemplo de GitHub, que usaban el estado del componente local, se veían así:

updateQuantityInCart (lineItemId, cantidad) {
  const checkoutId = this.state.checkout.id
  const lineItemsToUpdate = [{id: lineItemId, cantidad: parseInt (cantidad, 10)}]
devuelva this.props.client.checkout.updateLineItems (checkoutId, lineItemsToUpdate) .then (res => {
    this.setState ({
      pago y envío: res,
    });
  });
}
removeLineItemInCart (lineItemId) {
  const checkoutId = this.state.checkout.id
devuelva this.props.client.checkout.removeLineItems (checkoutId, [lineItemId]). luego (res => {
    this.setState ({
      pago y envío: res,
    });
  });
}
handleCartClose () {
  this.setState ({
    isCartOpen: falso,
  });
}

Podemos refactorizarlos para enviar eventos a la tienda Redux de la siguiente manera:

updateQuantityInCart (lineItemId, cantidad) {
    estado const = store.getState (); // estado de la tienda redux
    const checkoutId = state.checkout.id
    const lineItemsToUpdate = [{id: lineItemId, cantidad: parseInt (cantidad, 10)}]
    state.client.checkout.updateLineItems (checkoutId, lineItemsToUpdate) .then (res => {
      store.dispatch ({type: 'UPDATE_QUANTITY_IN_CART', carga útil: {checkout: res}});
    });
}
removeLineItemInCart (lineItemId) {
    estado const = store.getState (); // estado de la tienda redux
    const checkoutId = state.checkout.id
    state.client.checkout.removeLineItems (checkoutId, [lineItemId]). luego (res => {
      store.dispatch ({type: 'REMOVE_LINE_ITEM_IN_CART', carga útil: {checkout: res}});
    });
}
handleCartClose () {
    store.dispatch ({type: 'CLOSE_CART'});
}
handleCartOpen () {
    store.dispatch ({type: 'OPEN_CART'});
}

Si seguía, ya mencioné que agregué mi propia función handleCartOpen, porque paso esa función como accesorio a mi componente