Skip to content

fix(app): support CSF v3 stories in composeEnhancers (#602)#632

Open
darioabc wants to merge 1 commit into
tajo:mainfrom
darioabc:fix/csf-v3-support
Open

fix(app): support CSF v3 stories in composeEnhancers (#602)#632
darioabc wants to merge 1 commit into
tajo:mainfrom
darioabc:fix/csf-v3-support

Conversation

@darioabc

@darioabc darioabc commented May 7, 2026

Copy link
Copy Markdown

Closes #602.

Background

Storybook's Component Story Format v3 (the default since Storybook 7, 2023) defines a story as a plain object:

export const Default: Story = {
  args: { variant: "primary" },
  render: (args) => <Button {...args} />,
};

Ladle today only handles CSF v2, where the named export is a React.FC with .args / .argTypes / .decorators attached as function properties. With CSF v3 stories, composeEnhancers passes the plain object to ArgsProvider, which does React.createElement(component, props) — and React 19 (and 17/18 alike) throws Element type is invalid: expected a string … or a class/function … but got: object.

Verified the failure in a 312-story production codebase — every story errored under Ladle 5.1.1 + React 19. The fix has been running locally via patch-package against an 841-story production sweep with 0 ArgsProvider errors and 0 navigation errors before this PR was filed.

Change

packages/ladle/lib/app/src/compose-enhancers.tsx: when module[storyName] is an object, resolve a real component before passing it to ArgsProvider:

  1. If storyExport.render is a function, wrap it as (props) => render(props) so createElement receives a function.
  2. Else if the meta has a .component, render meta.component with the merged args (covers CSF v3 args-only and empty stories).
  3. Else, fall back to a thin no-op renderer (defensive; not exercised by the production codebase).

CSF v2 stories (named-export-is-a-function) are unchanged — the typeof storyExport === "function" branch keeps the existing path.

Args / argTypes spread now walks storyExport.args / storyExport.argTypes (instead of relying on module[storyName].args); the new spread is symmetric for both shapes.

Test plan

  • Existing tests pass (pnpm typecheck, pnpm lint, unit tests under packages/ladle, all e2e fixtures).
  • New e2e/csf3 package with Playwright spec covering four CSF v3 shapes:
    • args-only (resolves to meta.component)
    • render-only (wraps renderFn)
    • render+args (renderFn receives merged args)
    • empty (defensive fallback / meta.component)
  • Final assertion that none of those four CSF v3 stories logs an "Element type is invalid" error.
  • CSF v2 fixtures in e2e/decorators continue to work (decorator chain, args, params, mock-date).

Out of scope

  • Updating Ladle's exported Story<P> typing (currently extends React.FC<P>) to accept the object form. The runtime fix is sufficient for codebases that import StoryObj from @storybook/react; a follow-up PR can broaden Ladle's own type to match Storybook's StoryObj<…>.
  • The TSSatisfiesExpression parser unwrap for named-identifier default exports (separate PR).

Storybook's Component Story Format v3 (the default since Storybook 7,
2023) describes a story as a plain object — { args?, render?, ... } —
rather than a React function component with property attachments. Ladle
previously passed module[storyName] (the object) directly as 'component'
to ArgsProvider, whose React.createElement(component, props) threw
'Element type is invalid: expected a string ... but got: object'.

Resolve the named export to a real component before passing it down:
  - typeof storyExport === 'function': CSF v2, use directly
  - typeof storyExport === 'object' with render fn: wrap renderFn
  - typeof storyExport === 'object' with meta.component: render
    meta.component with merged args (covers args-only / empty /
    decorators-only)
  - else: defensive fallback renderer

CSF v2 stories are unchanged. Args/argTypes spread reads from
storyExport symmetrically for both shapes.

Closes tajo#602.
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.

Implement support for CSF 3

1 participant