Shopify is a leading e-commerce platform that offers robust tools for merchants to manage their online stores. As a Shopify developer, building custom apps that enhance the functionality of Shopify stores can greatly benefit merchants by streamlining processes and providing more control over their inventory. In this tutorial, we’ll walk through how to build a Shopify app using Remix.js that displays products with images and includes features to edit and delete products directly from the app. We’ll cover everything from setting up your development environment to interacting with Shopify’s API using GraphQL and implementing a full-width table for product management.
Why Use Remix.js and Polaris for Shopify App Development?
Before we dive into the tutorial, let’s explore why Remix.js and Shopify Polaris are excellent choices for Shopify app development:
- Remix.js: Remix is a full-stack React framework that emphasizes server-side rendering (SSR) and optimized data fetching. It’s perfect for building dynamic, high-performance web applications, making it ideal for managing e-commerce platforms like Shopify.
- Shopify Polaris: Polaris is Shopify’s design system that provides a comprehensive set of React components. These components ensure that your app’s UI is consistent with Shopify’s design language, offering a seamless experience for users.
Setting Up Your Development Environment
To start building your Shopify app, you need to set up your development environment. Follow these steps:
- Install Node.js: Ensure that Node.js and npm (Node Package Manager) are installed on your machine.
- Create a Shopify App with Remix.js: Use the Shopify CLI to create a new app with Remix.js as the frontend framework:bashCopy code
shopify app create node -t remix
This command initializes a new Shopify app using Remix.js, laying the foundation for our product management features. - Install Shopify Polaris: Navigate to your app’s directory and install Polaris components:bashCopy code
npm install @shopify/polaris
- Set Up Remix.js: Ensure that your Remix.js environment is properly configured, including setting up routes, loaders, and actions to handle data fetching and user interactions.
Interacting with the Shopify API Using GraphQL
To display, edit, and delete Shopify products, we’ll interact with the Shopify API using GraphQL. Shopify’s GraphQL API provides a flexible way to manage products, orders, customers, and more.
Step 1: Configure API Access
Ensure your Shopify app is configured to access the Shopify API. In your shopify.config.js
file, include the necessary API credentials:
javascriptCopy codemodule.exports = {
apiKey: process.env.SHOPIFY_API_KEY,
apiSecretKey: process.env.SHOPIFY_API_SECRET,
scopes: ['read_products', 'write_products'],
shop: process.env.SHOPIFY_SHOP,
accessToken: process.env.SHOPIFY_ACCESS_TOKEN,
};
Make sure to store your API key and access token securely in environment variables.
Step 2: Create a GraphQL Query to Fetch Products
Create a utility function to fetch products from the Shopify API. In your utils
directory, create a file named fetchProducts.js
:
javascriptCopy codeimport { gql } from 'graphql-request';
import { shopifyClient } from './shopifyClient';
export async function fetchProducts() {
const query = gql`
{
products(first: 20) {
edges {
node {
id
title
descriptionHtml
handle
images(first: 1) {
edges {
node {
src
}
}
}
variants(first: 1) {
edges {
node {
id
price
inventoryQuantity
}
}
}
}
}
}
}
`;
const response = await shopifyClient.request(query);
return response.products.edges.map(edge => edge.node);
}
This function uses a GraphQL query to retrieve the first 20 products, including their ID, title, description, handle, image, price, and inventory quantity.
Displaying Products in a Full-Width Table
With the product data fetched, the next step is to display it in a full-width table using Polaris components. The table will include options to edit and delete products.
Step 1: Create the Products Page
Create a new component, ProductsPage.jsx
, in your components or pages directory. This component will display the products in a full-width table format:
javascriptCopy codeimport { Page, Card, DataTable, Thumbnail, Button } from '@shopify/polaris';
import { useLoaderData, useFetcher } from '@remix-run/react';
import { fetchProducts } from '../utils/fetchProducts';
export async function loader() {
const products = await fetchProducts();
return products;
}
function ProductsPage() {
const products = useLoaderData();
const fetcher = useFetcher();
const handleDelete = (productId) => {
fetcher.submit(
{ productId },
{ method: 'post', action: '/products/delete' }
);
};
const rows = products.map(product => [
<Thumbnail source={product.images.edges[0]?.node.src || 'https://via.placeholder.com/150'} alt={product.title} />,
product.title,
`$${product.variants.edges[0]?.node.price}`,
product.variants.edges[0]?.node.inventoryQuantity,
<Button onClick={() => fetcher.submit(null, { method: 'get', action: `/products/${product.id}/edit` })}>
Edit
</Button>,
<Button destructive onClick={() => handleDelete(product.id)}>Delete</Button>
]);
return (
<Page title="Products">
<Card>
<DataTable
columnContentTypes={['text', 'text', 'text', 'numeric', 'text', 'text']}
headings={['Image', 'Title', 'Price', 'Inventory', 'Edit', 'Delete']}
rows={rows}
fullWidth
/>
</Card>
</Page>
);
}
export default ProductsPage;
In this code:
- DataTable Component: The
DataTable
component from Polaris is used to display the product data in a full-width table. Each row includes the product image, title, price, inventory, and buttons for editing and deleting the product. - Thumbnail Component: Displays the product image. A placeholder image is used if no image is available.
- Button Component: Provides options to edit and delete products. The
Edit
button redirects to the product edit page, while theDelete
button triggers the deletion of the product.
Step 2: Implement the Delete Action
Create the action that handles product deletion. In your routes directory, create a file named products.delete.js
:
javascriptCopy codeimport { redirect } from '@remix-run/node';
import { shopifyClient } from '../../utils/shopifyClient';
import { gql } from 'graphql-request';
export async function action({ request }) {
const formData = await request.formData();
const productId = formData.get('productId');
const mutation = gql`
mutation DeleteProduct($id: ID!) {
productDelete(input: { id: $id }) {
deletedProductId
}
}
`;
await shopifyClient.request(mutation, { id: productId });
return redirect('/products');
}
This action performs the following steps:
- Receive the Product ID: It receives the product ID from the form data.
- Delete the Product: It uses a GraphQL mutation to delete the specified product from Shopify.
- Redirect: After deletion, the user is redirected back to the products list page.
Implementing the Edit Product Page
In addition to displaying and deleting products, your app should allow users to edit product details.
Step 1: Create the Edit Product Page
Create a new component, ProductEditPage.jsx
, in your components or pages directory:
javascriptCopy codeimport { Page, Card, FormLayout, TextField, Button } from '@shopify/polaris';
import { useLoaderData, useFetcher } from '@remix-run/react';
import { fetchProduct } from '../utils/fetchProduct';
export async function loader({ params }) {
const product = await fetchProduct(params.productId);
return product;
}
function ProductEditPage() {
const product = useLoaderData();
const fetcher = useFetcher();
const handleInputChange = (field, value) => {
fetcher.submit(
{ field, value },
{ method: 'post', action: `/products/${product.id}/edit` }
);
};
return (
<Page title={`Edit Product: ${product.title}`}>
<Card sectioned>
<FormLayout>
<TextField
label="Product Title"
value={product.title}
onChange={(value) => handleInputChange('title', value)}
/>
<TextField
label="Product Description"
value={product.descriptionHtml}
onChange={(value) => handleInputChange('descriptionHtml', value)}
multiline
/>
<TextField
label="Price"
value={product.variants.edges[0]?.node.price}
type="number"
onChange={(value) => handleInputChange('price', value)}
/>
<TextField
label="Inventory Quantity"
value={product.variants.edges[0]?.node.inventoryQuantity}
type="number"
onChange={(value) => handleInputChange('inventoryQuantity', value)}
/>
</FormLayout>
<Button primary onClick={() => fetcher.submit({ method: 'post',
Leave a Reply