npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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-util

Import

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 /> : null

useEffectWithoutFirst(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?: () => void
  • startAtResumeIfNeeded?: boolean
  • disableTickSecUpdate?: 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.