Skip to content

Actions: Fix HandlerFunction type to support async callback props#33864

Merged
Sidnioulz merged 6 commits intostorybookjs:nextfrom
mixelburg:fix/action-handler-async-return-type
Mar 4, 2026
Merged

Actions: Fix HandlerFunction type to support async callback props#33864
Sidnioulz merged 6 commits intostorybookjs:nextfrom
mixelburg:fix/action-handler-async-return-type

Conversation

@mixelburg
Copy link
Copy Markdown

@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 may now return Promise as well as void, enabling async handlers and broader compatibility in composed flows.
  • Tests

    • Added type-level tests asserting both async and sync handler signatures to improve type safety and guard against regressions.

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

coderabbitai bot commented Feb 18, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The HandlerFunction type's return type was changed from void to void | Promise<void>. A new TypeScript declaration test file was added to assert compatibility with both synchronous () => void and asynchronous () => Promise<void> handler signatures.

Changes

Cohort / File(s) Summary
HandlerFunction type
code/core/src/actions/models/HandlerFunction.ts
Updated export: HandlerFunction return type changed from void to `void
Type declaration tests
code/core/src/actions/models/HandlerFunction.test-d.ts
Added a new declaration test using expect-type to assert HandlerFunction matches both () => void and () => Promise<void> signatures.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes


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
Copy Markdown
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
Copy Markdown
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.

@storybook-app-bot
Copy link
Copy Markdown

storybook-app-bot bot commented Feb 23, 2026

Package Benchmarks

Commit: 31c27e9, ran on 4 March 2026 at 01:22:05 UTC

The following packages have significant changes to their size or dependencies:

@storybook/addon-vitest

Before After Difference
Dependency count 2 2 0
Self size 402 KB 391 KB 🎉 -11 KB 🎉
Dependency size 338 KB 338 KB 🎉 -3 B 🎉
Bundle Size Analyzer Link Link

storybook

Before After Difference
Dependency count 49 49 0
Self size 20.20 MB 20.41 MB 🚨 +211 KB 🚨
Dependency size 16.52 MB 16.52 MB 🎉 -2 B 🎉
Bundle Size Analyzer Link Link

@storybook/nextjs

Before After Difference
Dependency count 534 534 0
Self size 648 KB 648 KB 🎉 -404 B 🎉
Dependency size 59.86 MB 59.89 MB 🚨 +35 KB 🚨
Bundle Size Analyzer Link Link

@storybook/nextjs-vite

Before After Difference
Dependency count 92 92 0
Self size 1.12 MB 1.12 MB 🎉 -5 B 🎉
Dependency size 22.47 MB 22.50 MB 🚨 +35 KB 🚨
Bundle Size Analyzer Link Link

@storybook/react-native-web-vite

Before After Difference
Dependency count 124 124 0
Self size 30 KB 30 KB 🎉 -57 B 🎉
Dependency size 23.76 MB 23.79 MB 🚨 +35 KB 🚨
Bundle Size Analyzer Link Link

@storybook/react-vite

Before After Difference
Dependency count 82 82 0
Self size 35 KB 35 KB 🎉 -22 B 🎉
Dependency size 20.25 MB 20.29 MB 🚨 +35 KB 🚨
Bundle Size Analyzer Link Link

@storybook/react-webpack5

Before After Difference
Dependency count 271 271 0
Self size 24 KB 24 KB 🚨 +7 B 🚨
Dependency size 44.53 MB 44.56 MB 🚨 +35 KB 🚨
Bundle Size Analyzer Link Link

@storybook/cli

Before After Difference
Dependency count 183 183 0
Self size 779 KB 779 KB 🚨 +83 B 🚨
Dependency size 67.38 MB 67.59 MB 🚨 +211 KB 🚨
Bundle Size Analyzer Link Link

@storybook/codemod

Before After Difference
Dependency count 176 176 0
Self size 32 KB 32 KB 🚨 +34 B 🚨
Dependency size 65.90 MB 66.11 MB 🚨 +211 KB 🚨
Bundle Size Analyzer Link Link

create-storybook

Before After Difference
Dependency count 50 50 0
Self size 1.04 MB 1.04 MB 🎉 -165 B 🎉
Dependency size 36.72 MB 36.93 MB 🚨 +211 KB 🚨
Bundle Size Analyzer node node

@storybook/react

Before After Difference
Dependency count 58 57 🎉 -1 🎉
Self size 1.19 MB 1.23 MB 🚨 +36 KB 🚨
Dependency size 13.21 MB 12.94 MB 🎉 -264 KB 🎉
Bundle Size Analyzer Link Link

Copy link
Copy Markdown
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.test-d.ts (1)

4-8: Consider wrapping assertions in named test() blocks for cleaner reporter output.

The top-level assertions are valid — *.test-d.ts files are not run by Vitest, they are only statically analyzed by the compiler. However, named test() blocks make failures easier to identify in CI output and align with the pattern in other type-test files in the repo.

Optionally, adding a not.toExtend negative assertion for () => void return type would document the regression boundary and confirm the old behaviour was incompatible.

✨ Optional: named test blocks + negative regression guard
+import { test } from 'vitest';
 import { expectTypeOf } from 'vitest';
 import type { HandlerFunction } from './HandlerFunction';

-// Should be assignable to async callback props (the fixed case)
-expectTypeOf<HandlerFunction>().toExtend<() => Promise<void>>();
-
-// Should remain assignable to plain void callbacks
-expectTypeOf<HandlerFunction>().toExtend<() => void>();
+test('HandlerFunction is assignable to async callback props', () => {
+  expectTypeOf<HandlerFunction>().toExtend<() => Promise<void>>();
+});
+
+test('HandlerFunction remains assignable to plain void callbacks', () => {
+  expectTypeOf<HandlerFunction>().toExtend<() => void>();
+});
+
+test('HandlerFunction typed as void-only is NOT assignable to async callbacks (regression guard)', () => {
+  // A (...args: any[]) => void should not extend () => Promise<void>
+  expectTypeOf<(...args: any[]) => void>().not.toExtend<() => Promise<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 4 - 8,
Wrap the top-level type assertions for HandlerFunction in named test() blocks so
test reporters show clear failure locations: create one test() with a name like
"HandlerFunction is assignable to async callback" that runs
expectTypeOf<HandlerFunction>().toExtend<() => Promise<void>>(), and another
named test() like "HandlerFunction remains assignable to void callback" that
runs expectTypeOf<HandlerFunction>().toExtend<() => void>(); optionally add a
negative regression guard test using
expectTypeOf<HandlerFunction>().not.toExtend<() => void>() to document the prior
incompatible behavior.
🤖 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.test-d.ts`:
- Around line 4-8: Wrap the top-level type assertions for HandlerFunction in
named test() blocks so test reporters show clear failure locations: create one
test() with a name like "HandlerFunction is assignable to async callback" that
runs expectTypeOf<HandlerFunction>().toExtend<() => Promise<void>>(), and
another named test() like "HandlerFunction remains assignable to void callback"
that runs expectTypeOf<HandlerFunction>().toExtend<() => void>(); optionally add
a negative regression guard test using
expectTypeOf<HandlerFunction>().not.toExtend<() => void>() to document the prior
incompatible behavior.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 70baca1 and eb0cf6b.

📒 Files selected for processing (1)
  • code/core/src/actions/models/HandlerFunction.test-d.ts

@Sidnioulz Sidnioulz changed the title Fix HandlerFunction type to support async callback props Actions: Fix HandlerFunction type to support async callback props Feb 25, 2026
@Sidnioulz Sidnioulz self-assigned this Feb 25, 2026
Copy link
Copy Markdown
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.

♻️ Duplicate comments (1)
code/core/src/actions/models/HandlerFunction.ts (1)

1-1: ⚠️ Potential issue | 🟠 Major

void | Promise<void> return type is not assignable to Promise<void> in strict TypeScript

The current type on Line 1 cannot be assigned to callback props expecting () => Promise<void>, leaving issue #23731 unresolved. To support the stated objective of allowing async callbacks without errors, use a fully permissive return type.

Proposed fix
-export type HandlerFunction = (...args: any[]) => void | Promise<void>;
+export type HandlerFunction = (...args: any[]) => any;
🤖 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, The
HandlerFunction type's return signature is too strict; change export type
HandlerFunction = (...args: any[]) => void | Promise<void>; to a fully
permissive return type so async callbacks can be assigned to Promise-returning
callbacks. Locate HandlerFunction and update its return to a permissive type
such as (...args: any[]) => any | Promise<any> (or use unknown/Promise<unknown>
if preferred) so synchronous void and async Promise results are both assignable
to () => Promise<void>.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@code/core/src/actions/models/HandlerFunction.ts`:
- Line 1: The HandlerFunction type's return signature is too strict; change
export type HandlerFunction = (...args: any[]) => void | Promise<void>; to a
fully permissive return type so async callbacks can be assigned to
Promise-returning callbacks. Locate HandlerFunction and update its return to a
permissive type such as (...args: any[]) => any | Promise<any> (or use
unknown/Promise<unknown> if preferred) so synchronous void and async Promise
results are both assignable to () => Promise<void>.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1218f59 and 52c4e9b.

📒 Files selected for processing (1)
  • code/core/src/actions/models/HandlerFunction.ts

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

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

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

3 participants