Skip to content

Commit

Permalink
Merge pull request #77 from ethanniser/react-scan-rerenders
Browse files Browse the repository at this point in the history
Fix react scan rerenders
  • Loading branch information
RhysSullivan authored Dec 2, 2024
2 parents 0a79dab + d1fc453 commit 0d170fd
Show file tree
Hide file tree
Showing 5 changed files with 46 additions and 49 deletions.
1 change: 1 addition & 0 deletions next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const nextConfig = {
experimental: {
ppr: true,
inlineCss: true,
reactCompiler: true,
},
typescript: {
ignoreBuildErrors: true,
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"@vercel/postgres": "^0.10.0",
"@vercel/speed-insights": "^1.0.12",
"ai": "^3.4.16",
"babel-plugin-react-compiler": "19.0.0-beta-df7b47d-20241124",
"bcryptjs": "^2.4.3",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
Expand Down
33 changes: 22 additions & 11 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,7 @@ select:focus,
textarea {
font-size: 16px;
}

[aria-label="Close toast"] {
background-color: white;
}
56 changes: 18 additions & 38 deletions src/components/ui/link.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import NextLink from "next/link";
import { useRouter } from "next/navigation";
import { useEffect, useRef, useState } from "react";
import { useEffect, useRef } from "react";

type PrefetchImage = {
srcset: string;
Expand Down Expand Up @@ -33,18 +33,15 @@ async function prefetchImages(href: string) {
}

const seen = new Set<string>();
const imageCache = new Map<string, PrefetchImage[]>();

export const Link: typeof NextLink = (({ children, ...props }) => {
const [images, setImages] = useState<PrefetchImage[]>([]);
const [preloading, setPreloading] = useState<(() => void)[]>([]);
const linkRef = useRef<HTMLAnchorElement>(null);
const router = useRouter();
let prefetchTimeout: NodeJS.Timeout | null = null; // Track the timeout ID
let prefetchTimeout: NodeJS.Timeout | null = null;

useEffect(() => {
if (props.prefetch === false) {
return;
}
if (props.prefetch === false) return;

const linkElement = linkRef.current;
if (!linkElement) return;
Expand All @@ -53,31 +50,32 @@ export const Link: typeof NextLink = (({ children, ...props }) => {
(entries) => {
const entry = entries[0];
if (entry.isIntersecting) {
// Set a timeout to trigger prefetch after 1 second
prefetchTimeout = setTimeout(async () => {
router.prefetch(String(props.href));
await sleep(0); // We want the doc prefetches to happen first.
void prefetchImages(String(props.href)).then((images) => {
setImages(images);
}, console.error);
// Stop observing once images are prefetched
await sleep(0);

if (!imageCache.has(String(props.href))) {
void prefetchImages(String(props.href)).then((images) => {
imageCache.set(String(props.href), images);
}, console.error);
}

observer.unobserve(entry.target);
}, 300); // 300ms delay
}, 300);
} else if (prefetchTimeout) {
// If the element leaves the viewport before 1 second, cancel the prefetch
clearTimeout(prefetchTimeout);
prefetchTimeout = null;
}
},
{ rootMargin: "0px", threshold: 0.1 }, // Trigger when at least 10% is visible
{ rootMargin: "0px", threshold: 0.1 },
);

observer.observe(linkElement);

return () => {
observer.disconnect(); // Cleanup the observer when the component unmounts
observer.disconnect();
if (prefetchTimeout) {
clearTimeout(prefetchTimeout); // Clear any pending timeouts when component unmounts
clearTimeout(prefetchTimeout);
}
};
}, [props.href, props.prefetch]);
Expand All @@ -88,19 +86,10 @@ export const Link: typeof NextLink = (({ children, ...props }) => {
prefetch={false}
onMouseEnter={() => {
router.prefetch(String(props.href));
if (preloading.length) return;
const p: (() => void)[] = [];
const images = imageCache.get(String(props.href)) || [];
for (const image of images) {
const remove = prefetchImage(image);
if (remove) p.push(remove);
}
setPreloading(p);
}}
onMouseLeave={() => {
for (const remove of preloading) {
remove();
prefetchImage(image);
}
setPreloading([]);
}}
onMouseDown={(e) => {
const url = new URL(String(props.href), window.location.href);
Expand Down Expand Up @@ -135,13 +124,4 @@ function prefetchImage(image: PrefetchImage) {
img.srcset = image.srcset;
img.src = image.src;
img.alt = image.alt;
let done = false;
img.onload = img.onerror = () => {
done = true;
};
return () => {
if (done) return;
img.src = img.srcset = "";
seen.delete(image.srcset);
};
}

0 comments on commit 0d170fd

Please sign in to comment.