Skip to content

Commit

Permalink
feat: add scroll loading more
Browse files Browse the repository at this point in the history
  • Loading branch information
patrikzita committed Jan 16, 2024
1 parent 76a6d49 commit d79a01e
Show file tree
Hide file tree
Showing 7 changed files with 205 additions and 4 deletions.
5 changes: 3 additions & 2 deletions .github/workflows/learn-github-actions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ jobs:
node-version: '18'
- run: npm install
- run: npm run lint
- run: npm run dev
- run: npm run dev &
- run: sleep 15
- run: npm run codegen
- run: npm run build
- name: Spellcheck
run: |
npm install -g cspell
cspell "**/*.{js,jsx,ts,tsx,md}"
cspell "**/*.{js,jsx,ts,tsx,md}"
11 changes: 9 additions & 2 deletions src/components/ExampleExplanation.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
import { cn } from "@/lib/utils";
import { ReactNode } from "react";

type Props = {
title: string;
description: string;
children?: ReactNode;
className?: string;
};

const ExampleExplanation = ({ title, description, children }: Props) => {
const ExampleExplanation = ({
title,
description,
children,
className,
}: Props) => {
return (
<div className="py-3">
<div className={cn("py-3", className)}>
<h1 className="font-bold">{title}</h1>
<p className="text-xs">{description}</p>
{children}
Expand Down
16 changes: 16 additions & 0 deletions src/components/TailwindIndicator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export function TailwindIndicator() {
if (process.env.NODE_ENV === "production") return null;

return (
<div className="fixed bottom-1 left-1 z-50 h-6 w-6 rounded-full bg-gray-800 flex justify-center items-center font-mono text-xs text-white">
<div className="block sm:hidden">xs</div>
<div className="hidden sm:block md:hidden lg:hidden xl:hidden 2xl:hidden">
sm
</div>
<div className="hidden md:block lg:hidden xl:hidden 2xl:hidden">md</div>
<div className="hidden lg:block xl:hidden 2xl:hidden">lg</div>
<div className="hidden xl:block 2xl:hidden">xl</div>
<div className="hidden 2xl:block">2xl</div>
</div>
);
}
10 changes: 10 additions & 0 deletions src/components/layouts/MainNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const ROUTES = {
BASED_PAGINATION_WITH_FILTERS: "/pagination-with-filters",
OFFSET_PAGINATION_EXPANDED: "/offset-pagination-expanded",
CREATE_DEAL: "/create-deal",
SCROLL_OFFSET_PAGINATION: "/scroll-offset-pagination",
};

const MainNav = () => {
Expand Down Expand Up @@ -46,6 +47,15 @@ const MainNav = () => {
Offset Pagination
</NavigationMenuLink>
</Link>
<Link
href={ROUTES.SCROLL_OFFSET_PAGINATION}
legacyBehavior
passHref
>
<NavigationMenuLink className={navigationMenuTriggerStyle()}>
Scroll offset pagination
</NavigationMenuLink>
</Link>
<Link
href={ROUTES.OFFSET_PAGINATION_EXPANDED}
legacyBehavior
Expand Down
40 changes: 40 additions & 0 deletions src/hooks/useIntersectionObserver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import * as React from "react";

interface Args extends IntersectionObserverInit {
freezeOnceVisible?: boolean;
}

export function useIntersectionObserver(
elementRef: React.RefObject<Element>,
{
threshold = 0,
root = null,
rootMargin = "0%",
freezeOnceVisible = false,
}: Args
): IntersectionObserverEntry | undefined {
const [entry, setEntry] = React.useState<IntersectionObserverEntry>();

const frozen = entry?.isIntersecting && freezeOnceVisible;

const updateEntry = ([entry]: IntersectionObserverEntry[]): void => {
setEntry(entry);
};

React.useEffect(() => {
const node = elementRef?.current; // DOM Ref
const hasIOSupport = !!window.IntersectionObserver;
if (!hasIOSupport || frozen || !node) return;

const observerParams = { threshold, root, rootMargin };
const observer = new IntersectionObserver(updateEntry, observerParams);
console.log(node);
observer.observe(node);

return () => observer.disconnect();

// eslint-disable-next-line react-hooks/exhaustive-deps
}, [elementRef?.current, threshold, root, rootMargin, frozen]);

return entry;
}
2 changes: 2 additions & 0 deletions src/pages/_app.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { TailwindIndicator } from "@/components/TailwindIndicator";
import MainNav from "@/components/layouts/MainNav";
import "@/styles/globals.css";
import { useApollo } from "@/utils/apolloClient";
Expand All @@ -13,6 +14,7 @@ export default function App({ Component, pageProps }: AppProps) {
<MainNav />
</div>
<Component {...pageProps} />
<TailwindIndicator />
</ApolloProvider>
);
}
125 changes: 125 additions & 0 deletions src/pages/scroll-offset-pagination/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import ExampleExplanation from "@/components/ExampleExplanation";
import { Button } from "@/components/ui/button";
import {
GetDealsOffsetBasedDocument,
useGetDealsOffsetBasedQuery,
} from "@/generated/graphql";
import { useIntersectionObserver } from "@/hooks/useIntersectionObserver";
import { addApolloState, initializeApollo } from "@/utils/apolloClient";
import { NetworkStatus } from "@apollo/client";
import { useCallback, useEffect, useRef } from "react";

const EXPLANATION = {
title: "Scroll Offset Pagination",
description: "Načítání dat podle pozice na stránce.",
};

export default function ScrollPaginationPage() {
const { data, fetchMore, loading, error, networkStatus } =
useGetDealsOffsetBasedQuery({
variables: {
limit: 2,
offset: 0,
},
notifyOnNetworkStatusChange: true,
});

const loadingMore = networkStatus === NetworkStatus.fetchMore;

const observerRef = useRef();

const lastDealElementRef = useCallback((node) => {
observerRef.current = node;
}, []);

const intersectionEntry = useIntersectionObserver(observerRef, {
threshold: 0.8,
freezeOnceVisible: false,
});

useEffect(() => {
if (intersectionEntry?.isIntersecting) {
loadMore();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [intersectionEntry?.isIntersecting]);

const loadMore = () => {
const currentLength = data.dealsOffsetBased.length || 0;
fetchMore({
variables: {
offset: currentLength,
limit: 2,
},
});
};

if (data?.dealsOffsetBased.length) {
return (
<main className="max-w-5xl mx-auto px-3 h-screen">
<ExampleExplanation
title={EXPLANATION.title}
description={EXPLANATION.description}
className="py-10"
>
<div className="py-1">
<h3 className="font-semibold">Poznámky</h3>
<ul className="text-xs space-y-2">
<li>
- v ideálním stavu by bylo potřeba ještě nastavit ať už se
nevolá loadMore v případě, že už nejsou další data k dispozici
</li>
</ul>
</div>
</ExampleExplanation>
<div className="py-10">
<div>
<span>Počet načtených dealů: {data.dealsOffsetBased.length}</span>
</div>
<div className="py-10">
{data.dealsOffsetBased.map((deal, index) => (
<div
key={deal.id}
className="h-72 border p-2"
ref={
index === data.dealsOffsetBased.length - 1
? lastDealElementRef
: null
}
>
<h1>{deal.title}</h1>
</div>
))}
</div>
{loadingMore && <div>Načítám další data</div>}
</div>
</main>
);
}

if (loading) {
return <div>loading</div>;
}

if (error) {
return <div>Error</div>;
}

return <div>Nic tu není</div>;
}

export async function getServerSideProps() {
const apolloClient = initializeApollo();

await apolloClient.query({
query: GetDealsOffsetBasedDocument,
variables: {
limit: 2,
offset: 0,
},
});

return addApolloState(apolloClient, {
props: {},
});
}

0 comments on commit d79a01e

Please sign in to comment.