Display Shopify Products with Images, Edit, and Delete in Remix.js | Shopify App Development

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:

  1. Install Node.js: Ensure that Node.js and npm (Node Package Manager) are installed on your machine.
  2. Create a Shopify App with Remix.js: Use the Shopify CLI to create a new app with Remix.js as the frontend framework:bashCopy codeshopify app create node -t remix This command initializes a new Shopify app using Remix.js, laying the foundation for our product management features.
  3. Install Shopify Polaris: Navigate to your app’s directory and install Polaris components:bashCopy codenpm install @shopify/polaris
  4. 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 the Delete 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

Your email address will not be published. Required fields are marked *