Created
June 15, 2020 00:29
-
-
Save chris-geelhoed/fd91edc11458d6cacf7e62b96828afce to your computer and use it in GitHub Desktop.
How to Paginate Results with Shopify's GraphQL API
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import axios from 'axios' | |
import React from 'react' | |
import { | |
Stack, | |
Thumbnail, | |
TextStyle, | |
Card, | |
Pagination, | |
Spinner, | |
TextContainer | |
} from '@shopify/polaris' | |
class ProductList extends React.Component { | |
constructor(props) { | |
super(props) | |
this.state = { | |
isLoading: false, | |
itemsPerPage: 5, | |
page: 0, | |
results: [], | |
productsById: {} | |
} | |
this.fetchData = this.fetchData.bind(this) | |
this.storeData = this.storeData.bind(this) | |
} | |
get pagedResults() { | |
const start = this.state.page * this.state.itemsPerPage | |
const end = start + this.state.itemsPerPage | |
return this.state.results.slice(start, end) | |
} | |
get pagedProductsWithoutData() { | |
return this.pagedResults.filter(id => !this.state.productsById[id]) | |
} | |
get pagedProductsWithData() { | |
return this.pagedResults.filter(id => this.state.productsById[id]) | |
} | |
async fetchData() { | |
this.setState({ isLoading: true }) | |
const params = { ids: this.pagedProductsWithoutData } | |
try { | |
const res = await axios.get('/api/products/data', { | |
params | |
}) | |
this.storeData(res.data) | |
} catch (e) { | |
console.error('Could not fetch products', e) | |
} | |
this.setState({ isLoading: false }) | |
} | |
async fetchResults() { | |
this.setState({ isLoading: true }) | |
try { | |
const res = await axios.get('/api/products/results') | |
console.log(res.data) | |
this.setState({ results: res.data }) | |
} catch (e) { | |
console.error('Could not fetch results', e) | |
} | |
this.setState({ isLoading: false }) | |
} | |
storeData(products) { | |
const productsById = Object.assign({}, this.state.productsById) | |
for (let product of products) { | |
productsById[product.id] = product | |
} | |
this.setState({ productsById }) | |
} | |
async componentDidMount() { | |
await this.fetchResults() | |
await this.fetchData() | |
} | |
componentDidUpdate(prevProps, prevState) { | |
if (prevState.page !== this.state.page && this.pagedProductsWithoutData.length > 0) { | |
this.fetchData() | |
} | |
} | |
render() { | |
const numPages = Math.ceil(this.state.results.length / this.state.itemsPerPage) | |
const cardContents = this.state.isLoading ? ( | |
<Stack | |
distribution="center" | |
alignment="center" | |
> | |
<div className="spinner-preview-container"> | |
<Spinner | |
accessibilityLabel="Loading affected products..." | |
size="large" | |
color="teal" | |
/> | |
</div> | |
</Stack> | |
) : ( | |
<Stack vertical> | |
<Stack vertical> | |
{this.pagedProductsWithData.map(id => { | |
const product = this.state.productsById[id] | |
return ( | |
<Stack | |
key={product.id} | |
vertical | |
spacing="extraTight" | |
> | |
<Stack | |
alignment="center" | |
distribution="leading" | |
> | |
<Thumbnail | |
source={product.featuredImage && product.featuredImage.transformedSrc} | |
alt={product.featuredImage && product.featuredImage.altText} | |
size="small" | |
/> | |
<h3>{product.title}</h3> | |
</Stack> | |
<TextContainer> | |
<TextStyle variation="subdued">{product.description}</TextStyle> | |
</TextContainer> | |
</Stack> | |
) | |
})} | |
</Stack> | |
<Stack | |
alignment="center" | |
distribution="equalSpacing" | |
> | |
<Pagination | |
hasPrevious={this.state.page > 0} | |
onPrevious={() => this.setState(state => ({ page: state.page - 1 }))} | |
hasNext={((this.state.page + 1) * this.state.itemsPerPage) < this.state.results.length} | |
onNext={() => this.setState(state => ({ page: state.page + 1 }))} | |
/> | |
<Stack | |
vertical | |
alignment="trailing" | |
spacing="extraTight" | |
> | |
<TextStyle> | |
Page {this.state.page + 1}/{numPages} | |
</TextStyle> | |
<TextStyle | |
variation="subdued" | |
> | |
(Max {this.state.itemsPerPage} products per page) | |
</TextStyle> | |
</Stack> | |
</Stack> | |
</Stack> | |
) | |
return ( | |
<Card | |
title="Products" | |
sectioned | |
> | |
<Stack vertical> | |
{this.state.results.length > 0 && <TextStyle>{this.state.results.length} results:</TextStyle>} | |
{cardContents} | |
</Stack> | |
</Card> | |
) | |
} | |
} | |
export default ProductList |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Handlers; | |
use Osiset\BasicShopifyAPI; | |
class QueryHandler | |
{ | |
public static function fetch(BasicShopifyAPI $api, array $product_ids): array | |
{ | |
$product_id_json = json_encode($product_ids); | |
$q = <<<QUERY | |
{ | |
nodes(ids: $product_id_json) { | |
...on Product { | |
title, | |
id, | |
description, | |
featuredImage { | |
altText, | |
transformedSrc | |
} | |
} | |
} | |
} | |
QUERY; | |
$request = $api->graph($q); | |
return $request->body->nodes; | |
} | |
public static function results(BasicShopifyAPI $api): array | |
{ | |
$ids = []; | |
$cursor = null; | |
do { | |
$args = [ | |
"first: 250" | |
]; | |
if (!empty($cursor)) { | |
$args[] = "after: \"$cursor\""; | |
} | |
$args = implode(', ', $args); | |
$q = <<<QUERY | |
{ | |
products($args) { | |
pageInfo { | |
hasNextPage, | |
hasPreviousPage | |
}, | |
edges { | |
cursor, | |
node { | |
id, | |
title | |
} | |
} | |
} | |
} | |
QUERY; | |
$request = $api->graph($q); | |
if (!empty($request->body->products->edges)) { | |
$edges = $request->body->products->edges; | |
foreach ($edges as $edge) { | |
$ids[] = $edge->node->id; | |
$cursor = $edge->cursor; | |
} | |
} | |
} while ($request->body->products->pageInfo->hasNextPage); | |
return $ids; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
any idea how to remake this using only node/graphql, but displaying orders this time instead of products.