Skip to content

feat(core): add useAsyncLock hook#381

Open
zztnrudzz13 wants to merge 1 commit into
mainfrom
feat/use-async-lock
Open

feat(core): add useAsyncLock hook#381
zztnrudzz13 wants to merge 1 commit into
mainfrom
feat/use-async-lock

Conversation

@zztnrudzz13
Copy link
Copy Markdown
Collaborator

Overview

  • Add useAsyncLock, a new core hook for preventing overlapping async work by skipping concurrent calls while a lock is held.
  • Return explicit execution results through runWithLock: { status: 'executed', data } when work runs and { status: 'blocked' } when a call is skipped.
  • Export the hook from the core package, add English/Korean docs, and include a minor changeset for react-simplikit.

Checklist

  • Did you write the test code?
  • Have you run yarn run fix to format and lint the code and docs?
  • Have you run yarn run test:coverage to make sure there is no uncovered line?
  • Did you write the JSDoc?

@zztnrudzz13 zztnrudzz13 requested a review from kimyouknow as a code owner June 1, 2026 17:12
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Jun 1, 2026

🦋 Changeset detected

Latest commit: ed78f03

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
react-simplikit Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 1, 2026

Size Change: 0 B 🆕

Total Size: 0 B

compressed-size-action

@kimyouknow
Copy link
Copy Markdown
Collaborator

Hi @zztnrudzz13 — thank you for this contribution, it's really well put together! Named functions, a stable useMemo/useCallback return, an SSR test, and solid coverage across the execute/block/reject/release paths. 🙏

There's one design point I'd love your input on before we merge, plus a tiny doc note:

1. Reactivity of isLocked()
Since the lock is stored in a useRef, isLocked() returns the current value but won't trigger a re-render when the lock changes. So the most natural usage — <button disabled={isLocked()}> — wouldn't visually update when a task starts or finishes. (I noticed your SubmitButton example nicely avoids this by checking result.status, so I suspect you're already aware!)

To be clear, the ref is absolutely the right call for the guard itself — switching to useState there would make the check-and-set depend on render timing and could let a quick second call slip through. So rather than changing that, maybe one of these?

  • Keep the ref for the synchronous guard, and additionally track a reactive boolean (e.g. useState) for UI, returning that boolean; or
  • If isLocked is meant for imperative/event-only use, keep the getter and just document that it doesn't trigger re-renders.

2. A small doc suggestion
runWithLock's callback and the { status: 'executed' | 'blocked' } result are the main thing people will reach for, but they currently only appear in prose. Exporting AsyncLockResult<T> and spelling out the callback/result contract in the JSDoc would make it easier to type against.

Everything else (coverage, SSR, JSDoc) looks great. Happy to talk through the API direction on point 1 anytime — thanks again!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants