Skip to content

Comments

Fix HandlerFunction type to support async callback props#33864

Open
mixelburg wants to merge 2 commits intostorybookjs:nextfrom
mixelburg:fix/action-handler-async-return-type
Open

Fix HandlerFunction type to support async callback props#33864
mixelburg wants to merge 2 commits intostorybookjs:nextfrom
mixelburg:fix/action-handler-async-return-type

Conversation

@mixelburg
Copy link

@mixelburg mixelburg commented Feb 18, 2026

Closes #23731

What I did

Changed the return type of HandlerFunction from void to any so that action handlers can be assigned to async callback props (e.g. onSubmit: () => Promise<void>) without causing type errors.

The issue is that (...args: any[]) => void is not assignable to (...args: any[]) => Promise<void>, but (...args: any[]) => any is, since any is compatible with all return types.

Checklist for Contributors

Testing

The changes in this PR are covered in the following automated tests:

  • stories
  • unit tests
  • integration tests
  • end-to-end tests

Manual testing

This is a type-only change. Verified that the existing HandlerFunction usages in the codebase (e.g. action() return type, ActionsMap, test stories) remain compatible.

Summary by CodeRabbit

  • Updates
    • Handler functions can now return values, enabling broader usage patterns and improved compatibility with integrations.
  • Tests
    • Added type-level tests to validate both async and void handler callback signatures.

Change return type from void to any so action handlers can be
assigned to async callback props without type errors.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 18, 2026

📝 Walkthrough

Walkthrough

The HandlerFunction type's return type was changed from void to any, and a TypeScript declaration test was added to assert compatibility with both () => void and () => Promise<void> handler signatures.

Changes

Cohort / File(s) Summary
HandlerFunction Type
code/core/src/actions/models/HandlerFunction.ts
Return type changed from void to any for HandlerFunction (export type HandlerFunction = (...args: any[]) => any).
Type declaration tests
code/core/src/actions/models/HandlerFunction.test-d.ts
New declaration test file added using expect-type to assert HandlerFunction matches () => void and () => Promise<void> signatures.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

✨ Finishing Touches
  • 📝 Generate docstrings

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
code/core/src/actions/models/HandlerFunction.ts (1)

1-1: any return type is the correct pragmatic fix, but consider adding a type-level regression test.

void is not assignable to Promise<void> in TypeScript's structural type system, and neither is unknown. any is the only standard escape hatch that makes HandlerFunction assignable to async callback props — the rationale is sound. It is also consistent with the pre-existing any[] parameter type.

The one concern is that any turns off return-type checking for callers that capture the return value of a HandlerFunction. In practice this is benign (action handlers are side-effect-only), but it's worth guarding against future regressions by adding a type-level test — for example a .test-d.ts file asserting HandlerFunction is assignable to () => Promise<void> and () => void:

import { expectTypeOf } from 'expect-type';
import type { HandlerFunction } from './HandlerFunction';

// Should be assignable to async callback props (the fixed case)
expectTypeOf<HandlerFunction>().toMatchTypeOf<() => Promise<void>>();

// Should remain assignable to plain void callbacks
expectTypeOf<HandlerFunction>().toMatchTypeOf<() => void>();

As per coding guidelines, TypeScript strict mode should be enabled across all packages — any usage should be minimised where possible, but there is no viable stricter alternative in this specific case.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code/core/src/actions/models/HandlerFunction.ts` at line 1, Add a type-level
regression test to lock in the `HandlerFunction` assignability: create a
`.test-d.ts` file that imports `HandlerFunction` and uses `expectTypeOf` (or
similar) to assert `HandlerFunction` is assignable to both `() => Promise<void>`
and `() => void`; reference the `HandlerFunction` symbol in the assertions so
future changes to `export type HandlerFunction = (...args: any[]) => any;` will
fail the type test if regressions occur.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@code/core/src/actions/models/HandlerFunction.ts`:
- Line 1: Add a type-level regression test to lock in the `HandlerFunction`
assignability: create a `.test-d.ts` file that imports `HandlerFunction` and
uses `expectTypeOf` (or similar) to assert `HandlerFunction` is assignable to
both `() => Promise<void>` and `() => void`; reference the `HandlerFunction`
symbol in the assertions so future changes to `export type HandlerFunction =
(...args: any[]) => any;` will fail the type test if regressions occur.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
code/core/src/actions/models/HandlerFunction.test-d.ts (1)

5-8: toMatchTypeOf is deprecated — use toExtend instead.

toMatchTypeOf has been deprecated since expect-type v1.2.0; the replacement is toExtend.

♻️ Proposed fix
-expectTypeOf<HandlerFunction>().toMatchTypeOf<() => Promise<void>>();
+expectTypeOf<HandlerFunction>().toExtend<() => Promise<void>>();
 
-expectTypeOf<HandlerFunction>().toMatchTypeOf<() => void>();
+expectTypeOf<HandlerFunction>().toExtend<() => void>();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code/core/src/actions/models/HandlerFunction.test-d.ts` around lines 5 - 8,
Replace deprecated expect-type matcher toMatchTypeOf with the new toExtend
matcher for the type assertions involving HandlerFunction; specifically update
the assertions that call expectTypeOf<HandlerFunction>().toMatchTypeOf<() =>
Promise<void>>() and expectTypeOf<HandlerFunction>().toMatchTypeOf<() => void>()
to use toExtend instead, keeping the same generic type arguments and intent so
the tests continue to assert assignability to both async and plain void
callbacks.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@code/core/src/actions/models/HandlerFunction.test-d.ts`:
- Line 1: Replace the deprecated expect-type import and matcher: change the
import of expectTypeOf in HandlerFunction.test-d.ts to come from 'vitest'
instead of 'expect-type', and update any usages of the old matcher toMatchTypeOf
to the new toExtend matcher (e.g., calls like
expectTypeOf(<type>).toMatchTypeOf(...) should become
expectTypeOf(<type>).toExtend(...)); ensure all occurrences in this test file
are updated so the file follows the same pattern as other type-test files.

---

Nitpick comments:
In `@code/core/src/actions/models/HandlerFunction.test-d.ts`:
- Around line 5-8: Replace deprecated expect-type matcher toMatchTypeOf with the
new toExtend matcher for the type assertions involving HandlerFunction;
specifically update the assertions that call
expectTypeOf<HandlerFunction>().toMatchTypeOf<() => Promise<void>>() and
expectTypeOf<HandlerFunction>().toMatchTypeOf<() => void>() to use toExtend
instead, keeping the same generic type arguments and intent so the tests
continue to assert assignability to both async and plain void callbacks.

@@ -0,0 +1,8 @@
import { expectTypeOf } from 'expect-type';
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check import patterns in existing test-d files
echo "=== Checking test-d.ts files for expectTypeOf imports ==="
rg -n "from ['\"]expect-type['\"]" --type=ts -g '*.test-d.ts' | head -20
echo ""
rg -n "from ['\"]vitest['\"]" --type=ts -g '*.test-d.ts' | head -20
echo ""
echo "=== Checking for toMatchTypeOf vs toExtend usage ==="
rg -n "toMatchTypeOf|toExtend" --type=ts -g '*.test-d.ts' | head -30

Repository: storybookjs/storybook

Length of output: 1565


🏁 Script executed:

#!/bin/bash
# Read the full test file to see what it's actually testing
echo "=== Full content of HandlerFunction.test-d.ts ==="
cat code/core/src/actions/models/HandlerFunction.test-d.ts

Repository: storybookjs/storybook

Length of output: 466


🏁 Script executed:

#!/bin/bash
# Check package.json to see what version of expect-type is installed
echo "=== expect-type version check ==="
rg '"expect-type"' package.json | head -5
echo ""
# Also check for vitest version
rg '"vitest"' package.json | head -5

Repository: storybookjs/storybook

Length of output: 99


🏁 Script executed:

#!/bin/bash
# Check expect-type version in yarn.lock or package-lock.json to see if v1.2.0+ is used
echo "=== Checking expect-type version in lock files ==="
rg -A2 '"expect-type"' yarn.lock | head -10
echo ""
# Also check for references to toExtend/toMatchTypeOf in package.json or docs
echo "=== Checking for expect-type changelog or docs references ==="
fd -e md -e txt | xargs rg -l "expect-type" | head -5

Repository: storybookjs/storybook

Length of output: 178


🌐 Web query:

expect-type toMatchTypeOf deprecation toExtend v1.2.0

💡 Result:

In expect-type, .toMatchTypeOf() is deprecated in favor of: .toMatchObjectType() (for “object has at least these keys” checks) or .toExtend() (for “is-a / subtype” checks). There are no foreseeable plans to remove .toMatchTypeOf(), but you’re encouraged to migrate because .toMatchObjectType() is stricter and .toExtend() is equivalent for “extends” semantics. [1]

Migration:

  • If you used .toMatchTypeOf<Expected>() to mean “Actual extends Expected” → use:

    expectTypeOf<Actual>().toExtend<Expected>()

    [1]

  • If you used .toMatchTypeOf<{ a: number }>() to allow extra object keys → use:

    expectTypeOf(value).toMatchObjectType<{ a: number }>()

    [1]

Source

  1. expect-type README (“Why is .toMatchTypeOf deprecated?” and matcher docs). [1]

🏁 Script executed:

#!/bin/bash
# Check what Vitest recommends by looking at Vitest test files in the repo
echo "=== Sample of how Vitest type tests are structured in recent files ==="
cat code/addons/pseudo-states/src/types.test-d.ts | head -50

Repository: storybookjs/storybook

Length of output: 1383


Update import source to 'vitest' and replace deprecated toMatchTypeOf with toExtend.

This file should align with the pattern used in other type-test files in the repo (e.g., code/addons/pseudo-states/src/types.test-d.ts):

  1. Import expectTypeOf from 'vitest' instead of 'expect-type'
  2. Replace toMatchTypeOf with toExtend (the deprecated API has been superseded in expect-type)
Suggested changes
-import { expectTypeOf } from 'expect-type';
+import { expectTypeOf } from 'vitest';
 import type { HandlerFunction } from './HandlerFunction';

 // Should be assignable to async callback props (the fixed case)
-expectTypeOf<HandlerFunction>().toMatchTypeOf<() => Promise<void>>();
+expectTypeOf<HandlerFunction>().toExtend<() => Promise<void>>();

 // Should remain assignable to plain void callbacks
-expectTypeOf<HandlerFunction>().toMatchTypeOf<() => void>();
+expectTypeOf<HandlerFunction>().toExtend<() => void>();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code/core/src/actions/models/HandlerFunction.test-d.ts` at line 1, Replace
the deprecated expect-type import and matcher: change the import of expectTypeOf
in HandlerFunction.test-d.ts to come from 'vitest' instead of 'expect-type', and
update any usages of the old matcher toMatchTypeOf to the new toExtend matcher
(e.g., calls like expectTypeOf(<type>).toMatchTypeOf(...) should become
expectTypeOf(<type>).toExtend(...)); ensure all occurrences in this test file
are updated so the file follows the same pattern as other type-test files.

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.

[Bug]: addon-actions action handler not assignable to async callback props

1 participant