Hooks
The React hooks the components here are built on — controlled/uncontrolled state, reduced motion, viewport, intersection.
Quick Preview
useUncontrolled
useIsMobile · useReducedMotion
Resize the window, or flip the OS motion setting, and both follow.
useIntersection
Scroll down…
Usage
Four hooks that were internal to this package, published because a component built on top of these needs the same behaviour and the same answers. Each is its own module, so importing one does not pull in the rest.
import { useUncontrolled } from "@giddaa-housing/ui/utils/use-uncontrolled";
import { useReducedMotion } from "@giddaa-housing/ui/utils/use-reduced-motion";
import { useIsMobile } from "@giddaa-housing/ui/utils/use-is-mobile";
import { useIntersection } from "@giddaa-housing/ui/utils/use-intersection";Examples
All four, live
useUncontrolled
useIsMobile · useReducedMotion
Resize the window, or flip the OS motion setting, and both follow.
useIntersection
Scroll down…
useUncontrolled
The controlled/uncontrolled pattern every component here follows: it works on its own, and hands over the moment a value prop arrives. Returns the value, a setter, and whether it is currently controlled.
function Counter({ value, defaultValue, onChange }) {
const [count, setCount, controlled] = useUncontrolled({
value,
defaultValue,
finalValue: 0, // used when neither value nor defaultValue is given
onChange,
});
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}When controlled, the setter is your onChange and nothing else — no internal state is written, so the component cannot drift from the value you passed.
useReducedMotion
Whether the OS asks for less motion. Read it rather than only writing motion-reduce: classes when the thing to change is behaviour rather than style — this package uses it to make a carousel jump between slides instead of animating, which no CSS override can do.
const reducedMotion = useReducedMotion();
element.scrollIntoView({ behavior: reducedMotion ? "auto" : "smooth" });It is false on the first render and correct after mount, so it will not mismatch during hydration.
useIsMobile
Whether the viewport is under a breakpoint, defaulting to 1024. For layout, prefer a CSS breakpoint; reach for this when the decision is structural — rendering a drawer instead of a sidebar, as Sidebar does, rather than restyling one.
const isMobile = useIsMobile(768);
return isMobile ? <Drawer>{nav}</Drawer> : <Sidebar>{nav}</Sidebar>;useIntersection
An IntersectionObserver as a ref callback. Returns the latest entry, so the full observer API — isIntersecting, intersectionRatio, boundingClientRect — is available rather than a boolean.
const { ref, entry } = useIntersection<HTMLDivElement>({ threshold: 0.9 });
useEffect(() => {
if (entry?.isIntersecting) loadMore();
}, [entry]);
return <div ref={ref} />;Props
| Prop/API | Type | Default | Description |
|---|---|---|---|
useUncontrolled | ({ value?, defaultValue?, finalValue?, onChange? }) => [value, setValue, controlled] | - | Controlled/uncontrolled state. finalValue is the fallback when neither value nor defaultValue is given. |
useReducedMotion | () => boolean | false before mount | Tracks prefers-reduced-motion: reduce. |
subscribeToReducedMotion | (onChange) => () => void | - | The subscription behind it, exported so it can be tested or used outside React. |
useIsMobile | (breakpoint?: number) => boolean | 1024 | Whether the viewport is narrower than breakpoint. |
DEFAULT_MOBILE_BREAKPOINT | number | 1024 | The default, exported so a layout can match it. |
useIntersection | (options?: IntersectionObserverInit) => { ref, entry } | - | Observes the element the ref lands on. entry is null before the first observation. |
Accessibility
useReducedMotionis the behavioural half of honouring a motion preference. Use it for anything CSS cannot reach — scroll behaviour, autoplay, animated transitions between routes — andmotion-reduce:classes for the rest.- Do not use
useIsMobileto hide content. A narrow viewport is not a smaller need; move or restructure rather than remove. - Content revealed by
useIntersectionshould still be reachable without scrolling — a keyboard user tabbing into a not-yet-observed region needs it to exist.