diff --git a/lib/Intersection.tsx b/lib/Intersection.tsx new file mode 100644 index 0000000..a20cb46 --- /dev/null +++ b/lib/Intersection.tsx @@ -0,0 +1,100 @@ +import { useRef, createElement, useEffect } from 'react' +import { isServer } from '@/utils' +import { display, DEFAULT_PROPS } from '@/constants' +import { useFallback } from '@/hooks' +import { hydrate } from 'react-dom' +import type { ReactHTML, DetailedHTMLProps, HTMLAttributes } from 'react' + +type IntersectionProps = { + children: JSX.Element + as?: T + /** When DOM is not exists, fallback to children or passed component */ + fallback?: false | JSX.Element + /** On fallback component is rendered, then fire */ + onFallback?: () => void + keepRender?: boolean +} & DetailedHTMLProps, HTMLElement> & + IntersectionObserverInit + +/** + * Lazy hydration component until intersection + * + * @example + * ```tsx + * + *
This do not hydrate
+ *
+ * ``` + */ +const Intersection = ({ + children, + fallback = children, + as, + onFallback, + style, + keepRender = true, + root, + rootMargin, + threshold, + ...props +}: IntersectionProps) => { + const ref = useRef(null) + const _as = as ?? 'div' + + useFallback( + ref, + { + fallback + }, + [] + ) + + useEffect(() => { + if (!ref.current) return + + const observer = new IntersectionObserver( + ([entry], obs) => { + if (entry.isIntersecting) { + hydrate(children, ref.current) + if (keepRender && ref.current) { + obs.unobserve(ref.current) + } + } else { + // keep + } + }, + { root, rootMargin, threshold } + ) + + if (ref.current) { + observer.observe(ref.current) + } + + return () => observer.disconnect() + }, [root, rootMargin, threshold]) + + if (isServer) + return createElement( + _as, + { + style: { + display, + ...style + }, + ...props + }, + children + ) + + return createElement(_as, { + ref, + style: { + display, + ...style + }, + ...props, + ...DEFAULT_PROPS + }) +} + +export default Intersection diff --git a/lib/Static.tsx b/lib/Static.tsx index 4837e53..efb16ba 100644 --- a/lib/Static.tsx +++ b/lib/Static.tsx @@ -4,7 +4,7 @@ import { display, DEFAULT_PROPS } from '@/constants' import { useFallback } from '@/hooks' import type { ReactHTML, DetailedHTMLProps, HTMLAttributes } from 'react' -type Props = { +type StaticProps = { children: JSX.Element as?: T /** When DOM is not exists, fallback to children or passed component */ @@ -30,7 +30,7 @@ const Static = ({ onFallback, style, ...props -}: Props) => { +}: StaticProps) => { const ref = useRef(null) const _as = as ?? 'div' diff --git a/lib/index.ts b/lib/index.ts index ca54f14..e83ca1c 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -1,3 +1,4 @@ import Static from '@/Static' +import Intersection from '@/Intersection' import { useFallback } from '@/hooks' -export { Static, useFallback } +export { Static, Intersection, useFallback }