Skip to content

@chalidade/react-hooks ​

React hooks kecil yang aman untuk SSR dan teruji. Setiap hook menjawab satu masalah yang sering salah ditulis sendiri: stale closure, listener yang lupa dilepas, atau hydration mismatch.

bash
pnpm add @chalidade/react-hooks

Belum dipublish

Package ini belum dipublish ke npm. Sementara ini, salin source-nya dari GitHub.

Membutuhkan React 18 atau lebih baru (useSyncExternalStore).

Ringkasan ​

HookUntuk apa
useDebouncedValueNilai yang menunggu input berhenti (search)
useDebouncedCallbackFungsi debounce yang stabil antar render (autosave)
useThrottledCallbackMaksimal sekali per interval (scroll, resize, drag)
useOnlineStatus koneksi untuk banner offline
useMediaQueryResponsif di JS tanpa hydration mismatch
useLocalStorageState yang tersimpan dan sinkron antar tab
useEventListenerListener yang otomatis dilepas
useOnClickOutsideMenutup dropdown/popover saat klik di luar
useIntersectionObserverLazy load, infinite scroll, animasi masuk
usePreviousNilai di render sebelumnya
useLatestRef berisi nilai terbaru (anti stale closure)
useIsomorphicLayoutEffectuseLayoutEffect tanpa peringatan di server

useDebouncedValue ​

tsx
const [input, setInput] = useState("");
const query = useDebouncedValue(input, 300);

useEffect(() => {
  if (query) void search(query);
}, [query]);

useDebouncedCallback ​

Identitas fungsinya tetap sama antar render, tapi selalu memanggil callback terbaru. Panggilan yang menunggu dibatalkan saat komponen unmount, jadi tidak ada setState setelah unmount.

tsx
const save = useDebouncedCallback((draft: string) => api.save(draft), 1000);

<textarea onChange={(e) => save(e.target.value)} onBlur={() => save.flush()} />

useThrottledCallback ​

Panggilan pertama langsung jalan, sisanya di jendela yang sama digabung menjadi satu panggilan di akhir jendela dengan argumen terbaru. Cocok untuk event yang terus menyala selama interaksi.

tsx
const onScroll = useThrottledCallback(() => setProgress(readScrollProgress()), 100);
useEventListener(window, "scroll", onScroll, { passive: true });

useOnline ​

tsx
const online = useOnline();
return online ? null : <p role="status">Kamu sedang offline. Perubahan disimpan lokal.</p>;

useMediaQuery ​

tsx
const isMobile = useMediaQuery("(max-width: 640px)");
const prefersReducedMotion = useMediaQuery("(prefers-reduced-motion: reduce)");

Di server nilainya false (atau argumen kedua). Karena memakai useSyncExternalStore, React memakai nilai server saat hydration lalu memperbaruinya, jadi tidak ada hydration mismatch.

useLocalStorage ​

tsx
const [theme, setTheme, resetTheme] = useLocalStorage<"light" | "dark">("theme", "light");
  • Komponen lain dengan key yang sama ikut ter-update.
  • Tab lain ikut ter-update lewat event storage.
  • Data rusak atau storage yang diblokir (mode privat) tidak membuat aplikasi crash: nilai kembali ke initialValue.

useEventListener ​

tsx
useEventListener(window, "keydown", (e) => {
  if (e.key === "Escape") close();
});

const ref = useRef<HTMLDivElement>(null);
useEventListener(ref, "scroll", onScroll, { passive: true });

Tipe event mengikuti target (KeyboardEvent untuk keydown, dst.).

useOnClickOutside ​

tsx
const ref = useRef<HTMLDivElement>(null);
useOnClickOutside(ref, () => setOpen(false));
return <div ref={ref}>{open && <Menu />}</div>;

Memakai pointerdown supaya juga bekerja di layar sentuh.

useIntersectionObserver ​

tsx
const { ref, isIntersecting } = useIntersectionObserver({ rootMargin: "200px", once: true });
return <img ref={ref} src={isIntersecting ? src : undefined} alt={alt} />;

usePrevious ​

tsx
const prevCount = usePrevious(count);
const direction = prevCount === undefined ? null : count > prevCount ? "naik" : "turun";

useLatest ​

tsx
const onTickRef = useLatest(onTick);
useEffect(() => {
  const id = setInterval(() => onTickRef.current(), 1000);
  return () => clearInterval(id);
}, []); // interval tidak perlu dipasang ulang saat onTick berubah

Belajar dari source-nya ​

Coba tulis useLocalStorage versi useState + useEffect sendiri, lalu jalankan test di packages/react-hooks. Kasus mana yang gagal? (Petunjuk: dua komponen dengan key yang sama, dan render pertama di server.)

Konsep terkait: 07 Framework, 17 Browser APIs, 18 Rendering Strategies.

MIT License