Skip to content

refactor: keep any out of implementation code - #571

Merged
hyesungoh merged 4 commits into
mainfrom
refactor/explicit-any-policy
Oct 23, 2026
Merged

hyesungoh merged 4 commits into
mainfrom
refactor/explicit-any-policy

Conversation

@hyesungoh

@hyesungoh hyesungoh commented Oct 19, 2026 •

Copy link
Copy Markdown
Member

Overview

Refs #568

The principles said "No any Types", but five callback files disabled no-explicit-any for the whole file and the rule was only a warning, so any could slip into implementation code unnoticed. The callback APIs do need any[] for their parameter list: unknown[] rejects annotated callbacks because function parameters are compared contravariantly.

This PR keeps the callback types as they are and makes the policy explicit:

  • no-explicit-any is now an error. Tests and _internal/test-utils keep it as a warning.
  • The one allowed any lives in CallerTypedArgs (src/_internal/types.ts). usePreservedCallback, useDebounce, useThrottle, useCallbackOncePerRender and the internal debounce/throttle use it, and their file-wide disables are gone.
  • The anys in useStorageState's internal helpers are now unknown, with the same type behavior.
  • design-principles.md (all locales), C4 of hook-design-principles.md (en, ko) and the agent instruction files describe the new rule.

Public type changes, covered by a minor changeset:

  • useThrottle now returns a function that takes the callback's parameters instead of any[], so a call with arguments the callback does not take is a type error.
  • useThrottle and useDebounce write out their return types, so a consumer that emits declarations can export their result without TS2742. useDebounce already failed this way in 0.3.3. The verify-pack consumer fixture now emits declarations and covers both.

Known limitation: passing an any-typed callback to usePreservedCallback makes its inferred type name CallerTypedArgs, which still fails with TS2742 in a consumer that emits declarations. Fixing it would mean exporting the internal any alias, so it is left as is.

Checklist

  • Did you write the test code? (no runtime change; existing tests cover these hooks at 100%)
  • 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? (public JSDoc unchanged except useThrottle's @template)

Raise no-explicit-any to an error outside tests and move the one allowed
`any` into the internal CallerTypedArgs type used by the callback APIs.
Reword the principle docs and agent instructions to match.

Refs #568
@changeset-bot

changeset-bot Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: c56dd31

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

github-actions Bot commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Size Change: +7 B (+0.01%)

Total Size: 115 kB

📦 View Changed
Filename Size Change
packages/react-simplikit/dist/hooks/useThrottle/useThrottle.cjs 868 B +4 B (+0.46%)
packages/react-simplikit/dist/hooks/useThrottle/useThrottle.mjs 841 B +3 B (+0.36%)
ℹ️ View Unchanged
Filename Size
packages/codemod/dist/cli.mjs 6.47 kB
packages/react-simplikit/dist/components/ImpressionArea/ImpressionArea.cjs 1.01 kB
packages/react-simplikit/dist/components/ImpressionArea/ImpressionArea.mjs 984 B
packages/react-simplikit/dist/components/Separated/Separated.cjs 686 B
packages/react-simplikit/dist/components/Separated/Separated.mjs 684 B
packages/react-simplikit/dist/components/SwitchCase/SwitchCase.cjs 723 B
packages/react-simplikit/dist/components/SwitchCase/SwitchCase.mjs 721 B
packages/react-simplikit/dist/hooks/useAsyncEffect/useAsyncEffect.cjs 625 B
packages/react-simplikit/dist/hooks/useAsyncEffect/useAsyncEffect.mjs 614 B
packages/react-simplikit/dist/hooks/useAvoidKeyboard/useAvoidKeyboard.cjs 985 B
packages/react-simplikit/dist/hooks/useAvoidKeyboard/useAvoidKeyboard.mjs 963 B
packages/react-simplikit/dist/hooks/useBodyScrollLock/useBodyScrollLock.cjs 887 B
packages/react-simplikit/dist/hooks/useBodyScrollLock/useBodyScrollLock.mjs 863 B
packages/react-simplikit/dist/hooks/useBooleanState/useBooleanState.cjs 546 B
packages/react-simplikit/dist/hooks/useBooleanState/useBooleanState.mjs 536 B
packages/react-simplikit/dist/hooks/useCallbackOncePerRender/useCallbackOncePerRender.cjs 815 B
packages/react-simplikit/dist/hooks/useCallbackOncePerRender/useCallbackOncePerRender.mjs 786 B
packages/react-simplikit/dist/hooks/useConditionalEffect/useConditionalEffect.cjs 980 B
packages/react-simplikit/dist/hooks/useConditionalEffect/useConditionalEffect.mjs 959 B
packages/react-simplikit/dist/hooks/useControlledState/useControlledState.cjs 1.04 kB
packages/react-simplikit/dist/hooks/useControlledState/useControlledState.mjs 1.03 kB
packages/react-simplikit/dist/hooks/useCounter/useCounter.cjs 1.03 kB
packages/react-simplikit/dist/hooks/useCounter/useCounter.mjs 1.01 kB
packages/react-simplikit/dist/hooks/useDebounce/debounce.cjs 463 B
packages/react-simplikit/dist/hooks/useDebounce/debounce.mjs 462 B
packages/react-simplikit/dist/hooks/useDebounce/useDebounce.cjs 1.28 kB
packages/react-simplikit/dist/hooks/useDebounce/useDebounce.mjs 1.26 kB
packages/react-simplikit/dist/hooks/useDebouncedCallback/useDebouncedCallback.cjs 1.15 kB
packages/react-simplikit/dist/hooks/useDebouncedCallback/useDebouncedCallback.mjs 1.12 kB
packages/react-simplikit/dist/hooks/useDebouncedValue/useDebouncedValue.cjs 1.21 kB
packages/react-simplikit/dist/hooks/useDebouncedValue/useDebouncedValue.mjs 1.18 kB
packages/react-simplikit/dist/hooks/useDoubleClick/useDoubleClick.cjs 965 B
packages/react-simplikit/dist/hooks/useDoubleClick/useDoubleClick.mjs 950 B
packages/react-simplikit/dist/hooks/useGeolocation/useGeolocation.cjs 2.02 kB
packages/react-simplikit/dist/hooks/useGeolocation/useGeolocation.mjs 2.02 kB
packages/react-simplikit/dist/hooks/useImpressionRef/useImpressionRef.cjs 1.25 kB
packages/react-simplikit/dist/hooks/useImpressionRef/useImpressionRef.mjs 1.22 kB
packages/react-simplikit/dist/hooks/useInputState/useInputState.cjs 672 B
packages/react-simplikit/dist/hooks/useInputState/useInputState.mjs 662 B
packages/react-simplikit/dist/hooks/useIntersectionObserver/useIntersectionObserver.cjs 1.1 kB
packages/react-simplikit/dist/hooks/useIntersectionObserver/useIntersectionObserver.mjs 1.07 kB
packages/react-simplikit/dist/hooks/useInterval/useInterval.cjs 837 B
packages/react-simplikit/dist/hooks/useInterval/useInterval.mjs 811 B
packages/react-simplikit/dist/hooks/useIsClient/useIsClient.cjs 585 B
packages/react-simplikit/dist/hooks/useIsClient/useIsClient.mjs 574 B
packages/react-simplikit/dist/hooks/useIsomorphicLayoutEffect/useIsomorphicLayoutEffect.cjs 646 B
packages/react-simplikit/dist/hooks/useIsomorphicLayoutEffect/useIsomorphicLayoutEffect.mjs 642 B
packages/react-simplikit/dist/hooks/useKeyboardHeight/useKeyboardHeight.cjs 736 B
packages/react-simplikit/dist/hooks/useKeyboardHeight/useKeyboardHeight.mjs 717 B
packages/react-simplikit/dist/hooks/useList/useList.cjs 952 B
packages/react-simplikit/dist/hooks/useList/useList.mjs 930 B
packages/react-simplikit/dist/hooks/useLoading/useLoading.cjs 919 B
packages/react-simplikit/dist/hooks/useLoading/useLoading.mjs 909 B
packages/react-simplikit/dist/hooks/useLongPress/useLongPress.cjs 1.71 kB
packages/react-simplikit/dist/hooks/useLongPress/useLongPress.mjs 1.68 kB
packages/react-simplikit/dist/hooks/useMap/useMap.cjs 762 B
packages/react-simplikit/dist/hooks/useMap/useMap.mjs 745 B
packages/react-simplikit/dist/hooks/useNetworkStatus/useNetworkStatus.cjs 1.61 kB
packages/react-simplikit/dist/hooks/useNetworkStatus/useNetworkStatus.mjs 1.59 kB
packages/react-simplikit/dist/hooks/useOutsideClickEffect/useOutsideClickEffect.cjs 810 B
packages/react-simplikit/dist/hooks/useOutsideClickEffect/useOutsideClickEffect.mjs 783 B
packages/react-simplikit/dist/hooks/usePageVisibility/usePageVisibility.cjs 1.28 kB
packages/react-simplikit/dist/hooks/usePageVisibility/usePageVisibility.mjs 1.25 kB
packages/react-simplikit/dist/hooks/usePreservedCallback/usePreservedCallback.cjs 751 B
packages/react-simplikit/dist/hooks/usePreservedCallback/usePreservedCallback.mjs 733 B
packages/react-simplikit/dist/hooks/usePreservedReference/usePreservedReference.cjs 805 B
packages/react-simplikit/dist/hooks/usePreservedReference/usePreservedReference.mjs 789 B
packages/react-simplikit/dist/hooks/usePrevious/usePrevious.cjs 643 B
packages/react-simplikit/dist/hooks/usePrevious/usePrevious.mjs 635 B
packages/react-simplikit/dist/hooks/useRefEffect/useRefEffect.cjs 836 B
packages/react-simplikit/dist/hooks/useRefEffect/useRefEffect.mjs 820 B
packages/react-simplikit/dist/hooks/useSafeAreaInset/useSafeAreaInset.cjs 942 B
packages/react-simplikit/dist/hooks/useSafeAreaInset/useSafeAreaInset.mjs 927 B
packages/react-simplikit/dist/hooks/useScrollDirection/useScrollDirection.cjs 1.35 kB
packages/react-simplikit/dist/hooks/useScrollDirection/useScrollDirection.mjs 1.34 kB
packages/react-simplikit/dist/hooks/useSet/useSet.cjs 1.03 kB
packages/react-simplikit/dist/hooks/useSet/useSet.mjs 1 kB
packages/react-simplikit/dist/hooks/useStorageState/storage.cjs 543 B
packages/react-simplikit/dist/hooks/useStorageState/storage.mjs 527 B
packages/react-simplikit/dist/hooks/useStorageState/useStorageState.cjs 973 B
packages/react-simplikit/dist/hooks/useStorageState/useStorageState.mjs 964 B
packages/react-simplikit/dist/hooks/useThrottle/throttle.cjs 431 B
packages/react-simplikit/dist/hooks/useThrottle/throttle.mjs 422 B
packages/react-simplikit/dist/hooks/useThrottledCallback/useThrottledCallback.cjs 1.02 kB
packages/react-simplikit/dist/hooks/useThrottledCallback/useThrottledCallback.mjs 993 B
packages/react-simplikit/dist/hooks/useThrottledValue/useThrottledValue.cjs 1.15 kB
packages/react-simplikit/dist/hooks/useThrottledValue/useThrottledValue.mjs 1.12 kB
packages/react-simplikit/dist/hooks/useTimeout/useTimeout.cjs 627 B
packages/react-simplikit/dist/hooks/useTimeout/useTimeout.mjs 600 B
packages/react-simplikit/dist/hooks/useToggle/useToggle.cjs 524 B
packages/react-simplikit/dist/hooks/useToggle/useToggle.mjs 506 B
packages/react-simplikit/dist/hooks/useVisibilityEvent/useVisibilityEvent.cjs 692 B
packages/react-simplikit/dist/hooks/useVisibilityEvent/useVisibilityEvent.mjs 674 B
packages/react-simplikit/dist/hooks/useVisualViewport/useVisualViewport.cjs 1.54 kB
packages/react-simplikit/dist/hooks/useVisualViewport/useVisualViewport.mjs 1.53 kB
packages/react-simplikit/dist/index.cjs 1.39 kB
packages/react-simplikit/dist/index.mjs 1.01 kB
packages/react-simplikit/dist/utils/buildContext/buildContext.cjs 864 B
packages/react-simplikit/dist/utils/buildContext/buildContext.mjs 844 B
packages/react-simplikit/dist/utils/disableBodyScrollLock/disableBodyScrollLock.cjs 667 B
packages/react-simplikit/dist/utils/disableBodyScrollLock/disableBodyScrollLock.mjs 660 B
packages/react-simplikit/dist/utils/enableBodyScrollLock/enableBodyScrollLock.cjs 663 B
packages/react-simplikit/dist/utils/enableBodyScrollLock/enableBodyScrollLock.mjs 652 B
packages/react-simplikit/dist/utils/getKeyboardHeight/getKeyboardHeight.cjs 609 B
packages/react-simplikit/dist/utils/getKeyboardHeight/getKeyboardHeight.mjs 602 B
packages/react-simplikit/dist/utils/getSafeAreaInset/getSafeAreaInset.cjs 902 B
packages/react-simplikit/dist/utils/getSafeAreaInset/getSafeAreaInset.mjs 893 B
packages/react-simplikit/dist/utils/isAndroid/isAndroid.cjs 543 B
packages/react-simplikit/dist/utils/isAndroid/isAndroid.mjs 534 B
packages/react-simplikit/dist/utils/isIOS/isIOS.cjs 753 B
packages/react-simplikit/dist/utils/isIOS/isIOS.mjs 746 B
packages/react-simplikit/dist/utils/isKeyboardVisible/isKeyboardVisible.cjs 431 B
packages/react-simplikit/dist/utils/isKeyboardVisible/isKeyboardVisible.mjs 419 B
packages/react-simplikit/dist/utils/isServer/isServer.cjs 381 B
packages/react-simplikit/dist/utils/isServer/isServer.mjs 379 B
packages/react-simplikit/dist/utils/mergeProps/mergeProps.cjs 645 B
packages/react-simplikit/dist/utils/mergeProps/mergeProps.mjs 643 B
packages/react-simplikit/dist/utils/mergeRefs/mergeRefs.cjs 790 B
packages/react-simplikit/dist/utils/mergeRefs/mergeRefs.mjs 787 B
packages/react-simplikit/dist/utils/subscribeKeyboardHeight/subscribeKeyboardHeight.cjs 1.12 kB
packages/react-simplikit/dist/utils/subscribeKeyboardHeight/subscribeKeyboardHeight.mjs 1.1 kB

compressed-size-action

@codecov-commenter

codecov-commenter commented Oct 19, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (cf0dd83) to head (c56dd31).

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff            @@
##              main      #571   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           69        70    +1     
  Lines         2281      2285    +4     
  Branches       737       738    +1     
=========================================
+ Hits          2281      2285    +4     
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@@ -0,0 +1,7 @@
/**
* Parameter list of a function the caller passes in.
* The caller's own annotations decide the types, and unannotated parameters end up as `any`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One small wording suggestion: could we scope “unannotated parameters end up as any” to the generic callback APIs using CallerTypedArgs, when no other type information is available?

For example, useInputState('', value => value.trim()) infers value as string without an explicit annotation.

Maybe we could say:
Some generic callback APIs use CallerTypedArgs to preserve compatibility. When no annotations or other inference context are available, callback parameters may fall back to any.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks 👍

useInputState takes a fixed (value: string) => string type, so it does not use CallerTypedArgs

but i think you are right the wording was too strong

so I updated 96f1bb5

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the clarification and the quick update! Looks good to me. 👍

Callback parameters take their types from annotations or the inference context, not from annotations alone, and fixed-type callbacks such as useInputState's never fall back to `any`.

@mnxmnz mnxmnz left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working on this!

It looks like consumer builds fail with TS2742. Could you take a look?

The emitted return type of useThrottle now references CallerTypedArgs from dist/_internal/types.mjs, but that path isn't exported, so packages that emit declarations seem to break.

// consumer, declaration: true
export function useScrollHandler() {
  return useThrottle((y: number) => console.log(y), 16);
}
// TS2742 ... cannot be named without a reference to '.../dist/_internal/types.mjs'

(It doesn't show up in this repo's workspaces, because they link to the local package. It shows up when the package is installed from npm.)

Thanks in advance!

A consumer that emits declarations got TS2742 when it exported their
result: useThrottle's inferred type named CallerTypedArgs and
useDebounce's named DebouncedFunction, and the package entry exports
neither. The verify-pack consumer fixture now emits declarations and
covers both.

useThrottle's returned function now takes the callback's parameters
instead of any[].
@hyesungoh

Copy link
Copy Markdown
Member Author

@mnxmnz Thanks, confirmed and fixed in c525f74 👍 👍
useThrottle and useDebounce now write out their return types. useDebounce turned out to fail the same way since 0.3.3. The verify-pack consumer fixture now emits declarations, so CI catches this from now on.

@hyesungoh
hyesungoh requested a review from mnxmnz October 21, 2026 04:04
@hyesungoh
hyesungoh merged commit a62e7d6 into main Oct 23, 2026
19 checks passed
@hyesungoh
hyesungoh deleted the refactor/explicit-any-policy branch October 23, 2026 01:48
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.

[Discussion] Should we change callback types or clarify the “No any Types” principle?

4 participants