Skip to content

Commit

Permalink
chore: infinite query for products list (#9)
Browse files Browse the repository at this point in the history
* chore: infinite query for products list

* chore: manually removing useless infinite queries

* chore: fixing display issue when navigating from orders

* chore: we should stop sending calls when there is no more data
  • Loading branch information
jpb06 authored Nov 26, 2022
1 parent 5097bfb commit 258a3f9
Show file tree
Hide file tree
Showing 21 changed files with 793 additions and 238 deletions.
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
libs/graphql/types/src/api.ts
libs/graphql/types/src/index.ts
apps/front/.next
.dockerignore
5 changes: 3 additions & 2 deletions .eslintrc.front.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
{
"plugins": ["jest-dom"],
"plugins": ["jest-dom", "@tanstack/query"],
"extends": [
"./.eslintrc.json",
"plugin:react/recommended",
"plugin:jest-dom/recommended"
"plugin:jest-dom/recommended",
"plugin:@tanstack/eslint-plugin-query/recommended"
],
"env": { "jest": true },
"settings": { "react": { "version": "detect" } },
Expand Down
12 changes: 12 additions & 0 deletions apps/api/src/graphql/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,12 @@ type GqlCategory {
products: [GqlProduct!]!
}

type GqlPaginatedProducts {
id: Int!
data: [GqlProduct!]!
hasMoreData: Boolean!
}

type GqlOrderedItem {
id: ID!
quantity: Int!
Expand Down Expand Up @@ -101,6 +107,7 @@ type GqlUserOrder {

type Query {
products: [GqlProduct!]!
productsByPage(pagination: GqlPaginationArgs!): GqlPaginatedProducts!
productsWithIds(ids: [Int!]!): [GqlProduct!]!
product(id: Int!): GqlProduct!
categories: [GqlCategory!]!
Expand All @@ -111,6 +118,11 @@ type Query {
myAddresses: [GqlAddress!]!
}

input GqlPaginationArgs {
offset: Int = 0
limit: Int = 25
}

type Mutation {
signup(email: String!, lastName: String!, firstName: String!, password: String!): GqlAuthOutput!
login(username: String!, password: String!): GqlAuthOutput!
Expand Down
10 changes: 10 additions & 0 deletions apps/api/src/modules/dtos/pagination-args.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Field, InputType, Int } from '@nestjs/graphql';

@InputType()
export class GqlPaginationArgs {
@Field(() => Int)
offset = 0;

@Field(() => Int)
limit = 25;
}
29 changes: 29 additions & 0 deletions apps/api/src/modules/products/closures/get-paginated.closure.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Injectable } from '@nestjs/common';

import { DatabaseService, selectProduct } from '@backend/database';

import { GqlPaginationArgs } from '../../dtos/pagination-args.dto';
import { GetAllSelectType } from './get-all.closure';

@Injectable()
export class GetPaginatedClosure {
public static Include = selectProduct({
Category: true,
});

constructor(private readonly db: DatabaseService) {}

async fetch({
limit,
offset,
}: GqlPaginationArgs): Promise<[GetAllSelectType[], number]> {
return this.db.$transaction([
this.db.product.findMany({
include: GetPaginatedClosure.Include,
skip: offset,
take: limit,
}),
this.db.product.count(),
]);
}
}
16 changes: 16 additions & 0 deletions apps/api/src/modules/products/dtos/gql.paginated-products.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import 'reflect-metadata';
import { ObjectType, Field, Int } from '@nestjs/graphql';

import { GqlProduct } from './gql.product.dto';

@ObjectType()
export class GqlPaginatedProducts {
@Field(() => Int)
id: number;

@Field(() => [GqlProduct])
data: Array<GqlProduct>;

@Field(() => Boolean)
hasMoreData: boolean;
}
9 changes: 8 additions & 1 deletion apps/api/src/modules/products/products.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,19 @@ import { DatabaseModule } from '@backend/database';
import { CategoriesModule } from '../categories/categories.module';
import { GetAllClosure } from './closures/get-all.closure';
import { GetByClosure } from './closures/get-by.closure';
import { GetPaginatedClosure } from './closures/get-paginated.closure';
import { ProductsResolver } from './products.resolver';
import { ProductsService } from './products.service';

@Module({
imports: [DatabaseModule, forwardRef(() => CategoriesModule)],
providers: [ProductsService, ProductsResolver, GetByClosure, GetAllClosure],
providers: [
ProductsService,
ProductsResolver,
GetByClosure,
GetAllClosure,
GetPaginatedClosure,
],
exports: [ProductsService, ProductsResolver],
})
export class ProductsModule {}
13 changes: 12 additions & 1 deletion apps/api/src/modules/products/products.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@ import { Product, Category } from '@prisma/client';

import { CategoriesService } from '../categories/categories.service';
import { GqlCategory } from '../categories/dtos/gql.category.dto';
import { GqlPaginationArgs } from '../dtos/pagination-args.dto';
import { GqlProduct } from '../products/dtos/gql.product.dto';
import { ProductsService } from '../products/products.service';
import { GetAllSelectType } from './closures/get-all.closure';
import { GqlPaginatedProducts } from './dtos/gql.paginated-products.dto';

@Resolver(GqlProduct)
export class ProductsResolver {
Expand All @@ -21,10 +24,18 @@ export class ProductsResolver {
) {}

@Query(() => [GqlProduct], { name: 'products' })
async getAll(): Promise<Array<Product>> {
async getAll(): Promise<Array<GetAllSelectType>> {
return this.products.getAll();
}

@Query(() => GqlPaginatedProducts, { name: 'productsByPage' })
async getPaginatedProducts(
@Args({ name: 'pagination', type: () => GqlPaginationArgs })
pagination: GqlPaginationArgs
): Promise<GqlPaginatedProducts> {
return this.products.getPaginated(pagination);
}

@Query(() => [GqlProduct], { name: 'productsWithIds' })
async getProductsWithIds(
@Args('ids', { type: () => [Int] }) ids: Array<number>
Expand Down
18 changes: 18 additions & 0 deletions apps/api/src/modules/products/products.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,39 @@ import { Product } from '@prisma/client';

import { DatabaseService } from '@backend/database';

import { GqlPaginationArgs } from '../dtos/pagination-args.dto';
import { GetAllClosure, GetAllSelectType } from './closures/get-all.closure';
import { GetByClosure, GetBySelectType } from './closures/get-by.closure';
import { GetPaginatedClosure } from './closures/get-paginated.closure';
import { GqlPaginatedProducts } from './dtos/gql.paginated-products.dto';

@Injectable()
export class ProductsService {
constructor(
private readonly db: DatabaseService,
private readonly getAllClosure: GetAllClosure,
private readonly getPaginatedClosure: GetPaginatedClosure,
private readonly getByClosure: GetByClosure
) {}

async getAll(): Promise<Array<GetAllSelectType>> {
return this.getAllClosure.fetch();
}

async getPaginated(input: GqlPaginationArgs): Promise<GqlPaginatedProducts> {
const [data, count] = await this.getPaginatedClosure.fetch(input);

return {
id: data.length > 0 ? data.at(0).id : null,
data: data.map((product) => {
const { price, ...data } = product;

return { ...data, price: Number(price) };
}),
hasMoreData: data.length + input.offset < count,
};
}

async getByIds(ids: Array<number>): Promise<Array<Product>> {
return this.db.product.findMany({
where: {
Expand Down
24 changes: 21 additions & 3 deletions apps/front/src/components/specialized/shop/Shop.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useProductsQuery } from '@front/api';
import { useInfiniteProductsByPageQuery } from '@front/api';
import ErrorCircle from '@front/assets/icons/error-circle.svg';
import {
PageTitle,
Expand All @@ -9,7 +9,17 @@ import {
import { ArticlesList } from './articles-list/ArticlesList';

export const ShopRoot = () => {
const { status, data } = useProductsQuery();
const { status, data, fetchNextPage, hasNextPage, isFetchingNextPage } =
useInfiniteProductsByPageQuery(
{
offset: 0,
limit: 20,
},
{
getNextPageParam: (lastPage) =>
lastPage.productsByPage.hasMoreData === true ? true : undefined,
}
);

return (
<div className="z-10 flex-grow p-2 md:p-4">
Expand All @@ -18,7 +28,15 @@ export const ShopRoot = () => {
{
{
loading: <GlobalCircularLoader>Loading</GlobalCircularLoader>,
success: <ArticlesList products={data?.products} />,
success: (
<ArticlesList
pages={data?.pages}
pageParams={data?.pageParams}
fetchNextPage={fetchNextPage}
hasNextPage={hasNextPage}
isLoading={isFetchingNextPage}
/>
),
error: (
<GlobalIndicator Icon={ErrorCircle}>
An error occured while fetching articles
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,46 @@
import { ProductsQueryData } from '@front/api';
import { Fragment } from 'react';

import { ProductsByPageQuery } from '@front/api';

import {
LoadMoreProducts,
LoadMoreProductsProps,
} from '../load-more-products/LoadMoreProducts';
import { Article } from './article/Article';

type ArticlesListProps = {
products?: ProductsQueryData;
};
interface ArticlesListProps extends Omit<LoadMoreProductsProps, 'hasMoreData'> {
pages?: ProductsByPageQuery[];
}

export const ArticlesList = ({ products }: ArticlesListProps) => {
if (!products) {
export const ArticlesList = ({
pages,
pageParams,
fetchNextPage,
hasNextPage,
isLoading,
}: ArticlesListProps) => {
if (!pages) {
return null;
}

return (
<div className="grid place-content-center gap-3 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5">
{products.map((p) => (
<Article key={p.id} {...p} />
))}
</div>
<>
<div className="grid place-content-center gap-3 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5">
{pages.map(({ productsByPage }) => (
<Fragment key={productsByPage.id}>
{productsByPage.data.map((p) => (
<Article key={p.id} {...p} />
))}
</Fragment>
))}
</div>
<LoadMoreProducts
hasMoreData={pages.at(-1)?.productsByPage.hasMoreData}
fetchNextPage={fetchNextPage}
hasNextPage={hasNextPage}
pageParams={pageParams}
isLoading={isLoading}
/>
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
query ProductsByPage($offset: Int!, $limit: Int!) {
productsByPage(pagination: { offset: $offset, limit: $limit }) {
id
data {
id
name
description
image
price
stock
category {
id
name
}
}
hasMoreData
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
query Product($id: Int!) {
product(id: $id) {
query Products {
products {
id
name
description
image
price
stock
category {
id
name
Expand Down
Loading

1 comment on commit 258a3f9

@vercel
Copy link

@vercel vercel bot commented on 258a3f9 Nov 26, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

graphql-shop – ./

graphql-shop-git-main-jpb06.vercel.app
graphql-shop-jpb06.vercel.app
graphql-shop-seven.vercel.app

Please sign in to comment.