@mj-studio/react-util
v0.0.33
Published
A React utility set by MJ Studio
Readme
@mj-studio/react-util
A manually maintained reference for the public React utilities in this repository.
Keep it aligned with index.ts, implementation behavior, and llms.txt.
Installation
pnpm add @mj-studio/react-utilImport
import { useTicker } from '@mj-studio/react-util'API
Hook
useMount(callback: EffectCallback): void
Runs a callback once after the component mounts. The callback return value is ignored.
useMount(() => {
analytics.track('screen_open')
})useIntervalCallback(callback: () => void, intervalSec?: number, doImmediately?: boolean): void
Runs a callback on a fixed interval and always uses the latest callback reference.
useIntervalCallback(() => {
refreshClock()
}, 1, true)useTimeoutHandlers(): { clearTimerAtUnmount, clearAllTimers, setAutoClearTimeout }
Returns timeout helpers that automatically clean up tracked timers on unmount.
const { setAutoClearTimeout, clearAllTimers } = useTimeoutHandlers()
useMount(() => {
setAutoClearTimeout(() => {
setOpen(false)
}, 3000)
})clearAllTimers()
Clears every tracked timeout and resets the internal timer list.
clearTimerAtUnmount(id, options?)
Registers an existing timeout id so it is cleared on unmount. Pass { withClear: true } to clear already tracked timers first.
setAutoClearTimeout(callback, ms, options?)
Creates a timeout, tracks it automatically, and returns the timeout id.
useStableCallback<T extends Function>(unstableCallback: T): T
Returns a stable callback reference that always delegates to the latest callback implementation.
const onTick = useStableCallback(() => {
console.log(latestValue)
})useUnmount(callback: EffectCallback): void
Runs a callback once when the component unmounts. The callback should perform its own cleanup work.
useUnmount(() => {
socket.close()
})useMountBeforeRender(callback: EffectCallback): void
Runs a callback once during the first render before the component paints. The callback return value is ignored.
useMountBeforeRender(() => {
cacheRef.current = createCache()
})useIsClient(): boolean
Returns true after the component has mounted on the client.
const isClient = useIsClient()
return isClient ? <ClientOnlyChart /> : nulluseEffectWithoutFirst(callback: EffectCallback, deps?: DependencyList): void
Runs an effect only after the initial render has been skipped. The callback return value is ignored.
useEffectWithoutFirst(() => {
saveDraft(formState)
}, [formState])useLifecycle(): { checkMounted: () => boolean; checkUnmounted: () => boolean }
Returns predicates for checking whether the component has mounted or already unmounted.
const { checkUnmounted } = useLifecycle()
fetchData().then(() => {
if (!checkUnmounted()) {
setReady(true)
}
})useRefValue<T>(init: () => T): T
Lazily creates a stable value once and keeps it for the component lifetime.
const instanceId = useRefValue(() => crypto.randomUUID())useBeforeunloadDom(handler: (e: BeforeUnloadEvent) => string | undefined | void): void
Subscribes to the browser beforeunload event. This hook is browser-only and throws when the required DOM APIs are unavailable.
useBeforeunloadDom((event) => {
if (!hasUnsavedChanges) {
return
}
event.preventDefault()
return 'You have unsaved changes.'
})Component
IntervalHandler(props: IntervalHandlerProps)
Renders the current interval tick through a render prop.
<IntervalHandler intervalSec={1} doImmediately={true}>
{({ tick }) => <span>{tick}</span>}
</IntervalHandler>BeforeunloadDom(props: { onBeforeunload: (e: BeforeUnloadEvent) => string | undefined | void; children?: ReactNode })
Registers a beforeunload listener and renders children unchanged.
<BeforeunloadDom onBeforeunload={() => 'You have unsaved changes.'}>
<Editor />
</BeforeunloadDom>Ticker
Ticker
Imperative ticker that emits elapsed time on a fixed interval.
const ticker = new Ticker()
ticker.start({
handler: (elapsedSec) => {
console.log(elapsedSec)
},
})Ticker.status: TickerStatus
Current lifecycle state of the ticker. The value is one of 'initial', 'pause', or 'progress'.
Ticker.start({ handler, intervalSec, tickMillis })
Starts the ticker from zero with a handler and optional interval settings.
Ticker.resume()
Resumes ticking from the current accumulated time.
Ticker.pause()
Pauses ticking and preserves the accumulated elapsed time.
Ticker.reset()
Stops the ticker and resets its accumulated time to zero.
useTicker(params?: UseTickerParams)
Creates ticker state and imperative controls for elapsed time updates.
const { tickSec, startTicker, pauseTicker } = useTicker({
onComplete: () => {
console.log('done')
},
})
useMount(() => {
startTicker({ durationSec: 10 })
})status
Ticker lifecycle state. The value is one of 'initial', 'run_pause', 'run_progress', or 'complete'.
tickSec
Current elapsed time in ticker units.
startTicker({ durationSec, intervalSec, tickMillis })
Starts the ticker. durationSec defaults to a large sentinel value so the ticker can run without an explicit end.
pauseTicker()
Pauses the active ticker when it is running.
resumeTicker()
Resumes a paused ticker. If startAtResumeIfNeeded is enabled, this can start a fresh ticker from the initial state.
resetTicker()
Resets the ticker state and clears the elapsed time.
TickerComponent
Render-prop component that subscribes to ticker updates and renders { tickSec }.
<TickerComponent>
{({ tickSec }) => <span>{tickSec}</span>}
</TickerComponent>useReverseTicker(params?: UseTickerParams)
Creates ticker controls that count down from a duration instead of counting up from zero.
const { tickSec, startTicker } = useReverseTicker({})
useMount(() => {
startTicker({ durationSec: 30 })
})tickSec
Remaining time derived from the original duration minus the elapsed ticker time.
startTicker({ durationSec, intervalSec, tickMillis })
Starts the reverse ticker. Negative durations are ignored.
resetTicker()
Resets the reverse ticker and clears the stored duration.
useDueDateTicker(params?: DueDateTickerProps)
Creates countdown text and controls for a target due date.
const { dueDateText, startTickerWithISO8601 } = useDueDateTicker({
secondsFormat: 'mm:ss',
})
useMount(() => {
startTickerWithISO8601('2030-01-01T00:00:00.000Z')
})dueDateText
Formatted remaining time text generated with @mj-studio/js-util second-format helpers.
tickSec
Remaining seconds from the target due date.
isExpired
Whether the target date has already passed or the countdown completed.
startTickerWithUnixSec(targetUnixSec)
Starts the countdown from a unix timestamp. Thirteen-digit millisecond values are normalized to seconds automatically.
startTickerWithISO8601(iso8601)
Starts the countdown from an ISO-8601 date string. Invalid strings are ignored.
DueDateText(props: { dueDate: string | number; children: (text: string, meta: { remainSeconds: number; isExpired: boolean }) => ReactElement } & DueDateTickerProps)
Renders formatted due-date text through a render prop.
<DueDateText dueDate={"2030-01-01T00:00:00.000Z"}>
{(text, { isExpired }) => <span>{isExpired ? 'Expired' : text}</span>}
</DueDateText>Utility
createCtx<T, P extends object>(delegate, name?): CreatedContext<T, P>
Creates a React context helper tuple with a required hook, provider, consumer, optional hook, and raw context.
Tuple order:
[useRequiredContext, Provider, Consumer, useOptionalContext, Context]
const [useAuth, AuthProvider] = createCtx<{ userId: string }, { userId: string }>(
({ userId }) => ({ userId }),
'Auth',
)getSearchParams(value: string[][] | Record<string, string> | string | URLSearchParams): string
Serializes search params into a query-string fragment without a leading ?.
getSearchParams({ page: '1', q: 'react' }) // Returns: 'page=1&q=react'DOM Utility
copyTextToClipboardDom(text: string): Promise<void>
Copies text to the system clipboard in a browser runtime.
await copyTextToClipboardDom('Hello world')copyImageToClipboardDom(dataURI: string): Promise<void>
Copies an image data URI to the system clipboard in a browser runtime.
await copyImageToClipboardDom('data:image/png;base64,...')blurFocusDom(): void
Moves focus away from the currently focused element in a browser runtime.
blurFocusDom()Event
AppEvent
Global in-memory event emitter instance for app-level events.
AppEvent.emitEvent('toast', { message: 'Saved' })AppEvent.emitEvent(type, payload?)
Emits an event to every registered listener and returns whether any listener was called.
AppEvent.awaitEmitEvent(type, payload?)
Emits an event and waits for asynchronous listeners in registration order.
AppEvent.addEventListener(type, listener)
Registers a listener for the given event type.
AppEvent.removeEventListener(type, listener)
Removes a previously registered listener from the given event type.
useAppEventListener<T>(type: string, listener: AppEventListener<T>, unsubscribe?: () => void): void
Subscribes a component to synchronous events from AppEvent.
useAppEventListener('toast', ({ message }) => {
console.log(message)
})useAsyncAppEventListener<T>(type: string, listener: AppEventAsyncListener<T>, unsubscribe?: () => void): void
Subscribes a component to asynchronous events intended for AppEvent.awaitEmitEvent.
useAsyncAppEventListener('save', async (payload) => {
await persist(payload)
})Types
UseTickerParams
Configuration for useTicker and useReverseTicker.
onComplete?: () => voidstartAtResumeIfNeeded?: booleandisableTickSecUpdate?: boolean
DueDateTickerProps
Configuration for useDueDateTicker and DueDateText.
secondsFormat?: SecFormats
TickerStatus
Ticker lifecycle state used by Ticker.
TickerHandler
Callback signature used by Ticker.start.
IntervalHandlerProps
Render-prop component props used by IntervalHandler.
ChildrenTransformer
Callback used by createCtx to transform provider children before render.
CreatedContext<T, P>
Readonly tuple returned by createCtx.
AppEventListener<T>
Synchronous listener signature used by AppEvent.
AppEventAsyncListener<T>
Asynchronous listener signature used by AppEvent.awaitEmitEvent.
