Skip to content

Instantly share code, notes, and snippets.

@jaredkc
Created January 19, 2024 22:17
Show Gist options
  • Save jaredkc/d51b18137b5945a7a0f0c12aad21d5c9 to your computer and use it in GitHub Desktop.
Save jaredkc/d51b18137b5945a7a0f0c12aad21d5c9 to your computer and use it in GitHub Desktop.
Shopify Remix app page to search products via GraphQL and display with Polaris components
import { json } from "@remix-run/node";
import {
Page,
Layout,
Text,
BlockStack,
ResourceList,
ResourceItem,
Thumbnail,
Badge,
Card,
Icon,
TextField,
Spinner,
} from "@shopify/polaris";
import { getSizedImageUrl } from "~/utility/shopify";
import { ImageMajor, SearchMinor } from "@shopify/polaris-icons";
import { authenticate } from "../shopify.server";
import {
Form,
useActionData,
useLoaderData,
useNavigation,
useSubmit,
} from "@remix-run/react";
import { useEffect, useRef, useState } from "react";
export const loader = async ({ request }) => {
const { admin } = await authenticate.admin(request);
const response = await admin.graphql(`#graphql
{
products(first: 5) {
nodes {
id
title
handle
status
featuredImage {
url
altText
}
}
}
}`);
const {
data: {
products: { nodes },
},
} = await response.json();
return json(nodes);
};
export const action = async ({ request }) => {
const { admin } = await authenticate.admin(request);
const formData = await request.formData();
const query = formData.get("query");
const response = await admin.graphql(
`#graphql
query products($query: String!) {
products(first: 5, query: $query) {
edges {
node {
id
title
handle
status
featuredImage {
url
altText
}
}
}
}
}`,
{ variables: { query: query } }
);
const {
data: {
products: { edges },
},
} = await response.json();
const products = edges.map((edge) => edge.node);
return json(products);
};
export default function ProductIndex() {
const productsDefault = useLoaderData();
const productsQuery = useActionData();
const products = productsQuery ?? productsDefault;
const navigation = useNavigation();
const [query, setQuery] = useState("");
const formRef = useRef(null);
const submit = useSubmit();
useEffect(() => {
if (query.length < 3) return;
const timeOutId = setTimeout(() => submit(formRef.current), 500);
return () => clearTimeout(timeOutId);
}, [query, submit]);
const renderSubmitting =
navigation.state === "submitting" ? <Spinner size="small" /> : null;
return (
<Page>
<ui-title-bar title="Product metafields" />
<Layout>
<Layout.Section>
<BlockStack gap="300">
<Form replace method="post" ref={formRef}>
<TextField
placeholder="Search products"
label="Search products"
prefix={<Icon source={SearchMinor} color="base" />}
suffix={renderSubmitting}
labelHidden
name="query"
autoComplete="off"
value={query}
onChange={setQuery}
selectTextOnFocus
/>
</Form>
{products.length === 0 && (
<Card>
<Text variant="bodyMd" as="p">
No products found. Try another search.
</Text>
</Card>
)}
<ProductList products={products} />
</BlockStack>
</Layout.Section>
</Layout>
</Page>
);
}
function ProductList({ products }) {
const renderItem = (item) => {
const { id, title, handle, status, featuredImage } = item;
const badgeStatus = status === "ACTIVE" ? "success" : "info";
const thumbSrc = featuredImage
? getSizedImageUrl(featuredImage.url, "58x58")
: ImageMajor;
const media = <Thumbnail source={thumbSrc} alt={title} size="small" />;
return (
<ResourceItem
id={id}
url={handle}
media={media}
accessibilityLabel={`View details for ${title}`}
>
<Text variant="bodyMd" fontWeight="bold" as="h3">
{title}
</Text>
<Badge status={badgeStatus} size="small">
{status}
</Badge>
</ResourceItem>
);
};
return (
<Card>
<ResourceList items={products} renderItem={(item) => renderItem(item)} />
</Card>
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment