Custom Hooks Example
Demonstrates the four hooks in src/common/hook: reach-bottom loading, cross-origin storage, captcha and safe redirect.
useReachBottom — reach-bottom loading
Scroll to the bottom to auto-load the next page (simulated pagination).
20 items · lastId = 20
useCrosStorage — cross-origin storage
Unified read/write across origins; falls back to localStorage/sessionStorage when STORAGE_PROXY is unset.
Result:—
useCaptcha — captcha
Click the image to refresh; NEXT_PUBLIC_CAPTCHA_URL is required to load the image.
Click the image to refresh
useNavigating — safe redirect
Builds redirect URLs with the correct locale prefix from useLocale.
Custom Hooks (src/common/hook) Usage Guide
Overview
src/common/hook/ bundles 4 reusable React hooks covering pagination, cross-origin storage, captcha and redirect:
| Hook | File | Purpose |
|---|---|---|
useReachBottom | ReachBottomHook.tsx | Auto-load next page when scrolled to the bottom (infinite scroll / pagination) |
useCrosStorage | CrosStorageHook.tsx | Unified storage read/write across origins (iframe postMessage) or same origin |
useCaptcha | CaptchaHook.tsx | Captcha image loading + click-to-refresh |
useNavigating | NavigatingHook.tsx | Locale-prefixed safe redirect |
All four are single-purpose and guard against SSR (typeof window === "undefined").
useReachBottom — reach-bottom loading
Listens to a scroll container's scroll event; when within 20px of the bottom it calls reachBottomHandler(lastId) to fetch the next page, using a globalLoading ref to debounce duplicate requests.
import useReachBottom from "@/common/hook/ReachBottomHook";
const { loading, lastId, containerRef } = useReachBottom(
async (lastId) => {
const page = await fetchNextPage(lastId); // returns a Promise
return page.newLastId; // resolve to the new cursor
},
0 // initialLastId
);
return <div ref={containerRef} className="h-96 overflow-y-auto">{/* list */}</div>;
Parameters
| Param | Type | Description |
|---|---|---|
| reachBottomHandler | (lastId: any) => Promise<any> | Bottom callback; resolves to the new lastId |
| initialLastId | any | Initial cursor |
Return value
| Field | Type | Description |
|---|---|---|
| loading | boolean | Whether loading |
| lastId | any | Current cursor |
| setLastId | (id) => void | Manually update the cursor |
| startLoading | () => void | Manually mark loading |
| finishLoading | (newLastId) => void | End loading and update the cursor |
| containerRef | RefObject<HTMLDivElement> | Attach to the scroll container |
Notes
- Threshold is 20px; after a request
globalLoadingresets 100ms later to avoid repeated triggers. - The inner
useEffectdepends on[loading]: it rebinds the scroll listener each timeloadingflips, so it always captures the latestlastIdinstead of a stale closure.
useCrosStorage — cross-origin storage
In cross-origin mode it talks to a proxy page through a hidden iframe + postMessage; in same-origin mode (or when STORAGE_PROXY is unset) it falls back to localStorage / sessionStorage. Typically used to share a login token across subdomains.
import useCrosStorage from "@/common/hook/CrosStorageHook";
const crosStorage = useCrosStorage(); // CrosStorage | undefined (undefined on first render)
await crosStorage?.set("token-value", "my_token"); // write
const v = await crosStorage?.get("my_token"); // read
await crosStorage?.remove("my_token"); // remove
Return value
Returns CrosStorage | undefined; the instance exposes:
| Method | Description |
|---|---|
| set(value, key?, storage?) | Write; key defaults to TOKEN_KEY, storage to AUTOMATIC |
| get(key?, storage?) | Read, resolves to a string or null |
| remove(key?, storage?) | Remove, resolves to the removed value |
| getToken / setToken / removeToken | Convenience methods for the token |
| locateToken(token?) | Locate / localize the current login user |
| destroy() | Remove the iframe (called automatically on unmount) |
storage values: LOCAL / SESSION / AUTOMATIC; AUTOMATIC is decided by NEXT_PUBLIC_TOKEN_STORAGE.
Note: the first render returns undefined (set inside a useEffect), so guard before use.
useCaptcha — captcha
Returns a ref to attach to an <img>: it auto-loads the captcha on mount and refreshes on click. After loading it sets the image visibility to visible.
import useCaptcha from "@/common/hook/CaptchaHook";
const captchaRef = useCaptcha();
return <img ref={captchaRef} alt="captcha" />;
Return value: RefObject<HTMLImageElement>.
Note: depends on NEXT_PUBLIC_CAPTCHA_URL; the image cannot load without it.
useNavigating — safe redirect
Builds redirect URLs with the correct locale prefix from useLocale(), so the locale is never lost.
import useNavigating from "@/common/hook/NavigatingHook";
const Navigate = useNavigating();
// or destructure a static method
const { redirectToLogin } = useNavigating();
Navigate.redirectToIndex(); // redirect home after 2s
Navigate.redirectTo("/some/path"); // redirect immediately
Navigate.redirectToLogin(true, 2000); // redirect to login after 2s (optionally with ref)
Static methods
| Method | Description |
|---|---|
| redirectToIndex(timeout=2000) | Delayed redirect to WWW_ROOT/{locale}/ |
| redirectTo(directUrl) | Immediate redirect; falls back to redirectToIndex when empty |
| redirectToLogin(withRef=true, timeout=2000) | Delayed redirect to PASSPORT_ROOT/{locale}{LOGIN_URL}; appends the current URL as ref when withRef is true |
Env vars: NEXT_PUBLIC_WWW_ROOT, NEXT_PUBLIC_PASSPORT_ROOT, NEXT_PUBLIC_LOGIN_URL.