Skip to content

Instantly share code, notes, and snippets.

@cuongboi
Created June 6, 2025 03:34
Show Gist options
  • Save cuongboi/be8e543c8a13e86c16e592e491afd194 to your computer and use it in GitHub Desktop.
Save cuongboi/be8e543c8a13e86c16e592e491afd194 to your computer and use it in GitHub Desktop.
import React, { useState, useRef, useEffect, ReactNode, MutableRefObject } from "react";
import { cn } from "@/lib/utils";
type Props = {
defaultHeight?: string;
verticalOffset?: number;
horizontalOffset?: number;
root?: MutableRefObject<HTMLElement | null>;
children: ReactNode;
as?: keyof JSX.IntrinsicElements;
classNames?: string;
placeholderChildren?: ReactNode;
defaultValue?: boolean;
shouldRecordHeights?: boolean;
useIdletime?: boolean;
forceRender?: boolean;
};
const RenderIfVisible: React.FC<Props> = (props) => {
const {
defaultHeight = "300px",
root,
verticalOffset = 50,
horizontalOffset = 0,
as = "div",
children,
classNames = "",
shouldRecordHeights = true,
//placeholder children
placeholderChildren = null, //placeholder children
defaultValue = false,
useIdletime = false,
forceRender = false,
} = props;
const [shouldVisible, setShouldVisible] = useState<boolean>(defaultValue);
const placeholderHeight = useRef<string>(defaultHeight);
const intersectionRef = useRef<HTMLElement | null>(null);
const isVisible = shouldVisible || forceRender;
// Set visibility with intersection observer
useEffect(() => {
if (intersectionRef.current) {
const observer = new IntersectionObserver(
(entries) => {
//DO no remove comments for future
if (typeof window !== undefined && window.requestIdleCallback && useIdletime) {
window.requestIdleCallback(() => setShouldVisible(entries[entries.length - 1].isIntersecting), {
timeout: 300,
});
} else {
setShouldVisible(entries[entries.length - 1].isIntersecting);
}
},
{
root: root?.current,
rootMargin: `${verticalOffset}% ${horizontalOffset}% ${verticalOffset}% ${horizontalOffset}%`,
}
);
observer.observe(intersectionRef.current);
return () => {
if (intersectionRef.current) {
// eslint-disable-next-line react-hooks/exhaustive-deps
observer.unobserve(intersectionRef.current);
}
};
}
}, [intersectionRef, children, root, verticalOffset, horizontalOffset]);
//Set height after render
useEffect(() => {
if (intersectionRef.current && isVisible && shouldRecordHeights) {
window.requestIdleCallback(() => {
if (intersectionRef.current) placeholderHeight.current = `${intersectionRef.current.offsetHeight}px`;
});
}
}, [isVisible, intersectionRef, shouldRecordHeights]);
const child = isVisible ? <>{children}</> : placeholderChildren;
const style = isVisible || !shouldRecordHeights ? {} : { height: placeholderHeight.current, width: "100%" };
const className = isVisible || placeholderChildren ? classNames : cn(classNames, "bg-custom-background-80");
return React.createElement(as, { ref: intersectionRef, style, className }, child);
};
export default RenderIfVisible;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment