Async & Visibility
Mount, Observer, Slacker, and useIntersectionObserver.
Async & Visibility
The async and visibility utilities in Utilinent are designed to handle data loading and rendering based on timing or viewport intersection. They form a dependency chain that starts from a low-level hook and scales up to opinionated layout components.
Dependency Chain
Understanding how these components relate helps you choose the right level of abstraction:
useIntersectionObserver: The low-level hook that tracks visibility.Observer: A component wrapper around the hook for "render-when-visible" behavior.Slacker: A higher-level component that usesObserverto gate async loading until a region is visible.Mount: A separate branch that handles async children regardless of visibility.
Mount
Mount treats async operations as a first-class rendering concern. It accepts regular nodes, functions that return nodes, or functions that return Promises.
import { Mount } from '@ilokesto/utilinent';
function AsyncProfile() {
return (
<Mount fallback={<p>Loading profile...</p>}>
{async () => {
const data = await fetchProfile();
return <section>{data.name}</section>;
}}
</Mount>
);
}Runtime Behavior
- Node path: If
childrenis already a React node,Mountresolves immediately to that node. - Function path: If
childrenis a function, it is executed inside an effect-driven path. The fallback stays visible until the result is resolved. - Error path: If the function throws or the promise rejects, the
fallbackremains andonErroris triggered. - Stale-call guard: If the component re-renders while a previous promise is still pending,
Mountignores the outdated result via its internal call-id guard.
Observer
Observer is the declarative way to render content only when it enters the viewport.
import { Observer } from '@ilokesto/utilinent';
function LazyContent() {
return (
<Observer triggerOnce fallback={<div style={{ height: 400 }} />}>
<ExpensiveComponent />
</Observer>
);
}Key Features
- Default Sizing: To ensure the intersection is actually detectable,
Observerdefaults todisplay: blockand injectsminHeight/minWidthwhen those values are absent. - Forwarded Ref: It correctly merges the internal observer ref with any ref you provide.
- Show-backed Rendering: Internally it renders through
Show.div, so intersection state becomes thewhencondition for rendering.
Slacker
Slacker is the most specialized component in this family. It combines visibility observation with an async loader.
import { Slacker } from '@ilokesto/utilinent';
function DeferredData() {
return (
<Slacker
loader={fetchData}
loadingFallback={<p>Loading...</p>}
errorFallback={({ retry }) => <button onClick={retry}>Retry</button>}
>
{(data) => <DataView data={data} />}
</Slacker>
);
}Internals
- Null Sentinel: It uses
nullas the internal state sentinel for "not yet visible/loaded". - Visibility-Gated: The
loaderis only triggered once the component enters the viewport (viaObserver). - Retry Logic: It supports
maxRetriesandretryDelayto handle transient network failures gracefully.
useIntersectionObserver
The underlying hook for visibility detection.
const { ref, isIntersecting, entry } = useIntersectionObserver({
threshold: 0.1,
rootMargin: '100px',
freezeOnceVisible: true,
});Notable Behaviors
- First-callback skip: The initial observer fire (which often happens on mount) is skipped to avoid false positives.
freezeOnceVisible: Mapping totriggerOncein higher components, this stops the browser observer after the first successful intersection.- Node Reset: If the observed DOM node is removed or cleared, the internal state resets appropriately.
Decision Guidance
- Use
Mountwhen: You have a standalone async operation that should start as soon as the component is mounted. - Use
Observerwhen: You want to delay rendering of large DOM trees to improve initial page load performance. - Use
Slackerwhen: You want to combine visibility-gated rendering with actual data fetching.
Common Mistakes
- Missing dimensions on fallback: If your
fallbackhas zero height, it might never "intersect" because it's technically outside the viewport or invisible. Always provide a placeholder height. - Ignoring the
triggerOncetrade-off: If you don't usetriggerOnce, the component will unmount every time it leaves the viewport, which might lead to repeated loading and lost state. - Circular visibility: An element that renders, becomes visible, expands its size, then pushes itself out of the viewport, creating a flicker loop.