Back to home

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).

Item 1
Item 2
Item 3
Item 4
Item 5
Item 6
Item 7
Item 8
Item 9
Item 10
Item 11
Item 12
Item 13
Item 14
Item 15
Item 16
Item 17
Item 18
Item 19
Item 20

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:

HookFilePurpose
useReachBottomReachBottomHook.tsxAuto-load next page when scrolled to the bottom (infinite scroll / pagination)
useCrosStorageCrosStorageHook.tsxUnified storage read/write across origins (iframe postMessage) or same origin
useCaptchaCaptchaHook.tsxCaptcha image loading + click-to-refresh
useNavigatingNavigatingHook.tsxLocale-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

ParamTypeDescription
reachBottomHandler(lastId: any) => Promise<any>Bottom callback; resolves to the new lastId
initialLastIdanyInitial cursor

Return value

FieldTypeDescription
loadingbooleanWhether loading
lastIdanyCurrent cursor
setLastId(id) => voidManually update the cursor
startLoading() => voidManually mark loading
finishLoading(newLastId) => voidEnd loading and update the cursor
containerRefRefObject<HTMLDivElement>Attach to the scroll container

Notes

  • Threshold is 20px; after a request globalLoading resets 100ms later to avoid repeated triggers.
  • The inner useEffect depends on [loading]: it rebinds the scroll listener each time loading flips, so it always captures the latest lastId instead 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:

MethodDescription
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 / removeTokenConvenience 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

MethodDescription
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.