argTypes table is not generated for components with React.forwardRef and index type in their interface (with typescript)
See original GitHub issueDescribe the bug Hello, dear Storybook dev team, please take my sincere appreciation and gratitude toward the top-notch tool that you’ve created.
I have started using Storybook recently in my existing project and bumped into this problem right away, with the very first component. Frankly speaking, I hesitate regarding the importance of this bug, but I personally have spent around 10h trying to figure out the problem and would like to save some time for anyone else who could stumble in the same situation.
The problem is that if you have a React component wrapped in React.forwardRef
and with index type in its interface, the arcTypes table in Storybook won’t be rendered. You may see the screenshots of my code below. I’ve also created a repo in my GitHub, so feel free to fork it.
So far, I’ve figured out that the issue happens only with React.forwardRef, but it’s possible that some other react utilities may have the same effect.
As a workaround I also have found that if you will use React.FC<ComponentProps>
typing for your component, this will remove the problem, even if the index type is still there.
To Reproduce
-
Clone or fork with repository - https://github.com/JoyTailor-1775/storybook-bug
-
Run
npm install
andnpm run storybook
-
See that no doc is created for the Button component;
-
Go to
src/types/Button.ts
and uncomment the 54th line (with index type); -
Restart the project and see the doc rendered;
System System: OS: macOS 11.2.3 CPU: (8) x64 Intel® Core™ i7-4770HQ CPU @ 2.20GHz Binaries: Node: 14.16.1 - /usr/local/bin/node Yarn: 1.22.4 - /usr/local/bin/yarn npm: 7.18.1 - /usr/local/bin/npm Browsers: Chrome: 91.0.4472.114 Safari: 14.0.3 npmPackages: @storybook/addon-actions: ^6.3.0-rc.11 => 6.3.0-rc.11 @storybook/addon-essentials: ^6.3.0-rc.11 => 6.3.0-rc.11 @storybook/addon-links: ^6.3.0-rc.11 => 6.3.0-rc.11 @storybook/builder-webpack5: ^6.3.0-rc.11 => 6.3.0-rc.11 @storybook/manager-webpack5: ^6.3.0-alpha.41 => 6.3.0-rc.11 @storybook/react: ^6.3.0-rc.11 => 6.3.0-rc.11
Additional context
Issue Analytics
- State:
- Created 2 years ago
- Reactions:3
- Comments:8
Top GitHub Comments
@honohunter I’m not sure this is the exact same workaround that @JoyTailor-1775 found, but I have a workaround that sounds similar. Assuming you have a component called
Button
that usesReact.forwardRef
:A few things to note here:
PropsType
from the component file.React.FC<PropsType>
.component
property of the default export, as well as the type generic forComponentMeta
.What this gets you is a new story under your component that lists all the props and a main entry in the Docs tab that shows the correct and complete props table. Also, stories that use the
Template: ComponentStory<T>
pattern will have the full props list in their controls tab.It’s worth noting that wrapping the new component in
ComponentStory
does not work:Hopefully this workaround helps you, and hopefully it also provides some info to anyone else about how to track down the bug that requires this workaround in the first place!
Made a little investigation and here’s what I found
TL;DR
While declaring button component, set it’s type this way:
Explanation
Based on React types
forwardRef
function looks this way:Basically it means that component returned by
forwardRef
should accept props of generic typeP
,ref
attribute of generic typeT
and few other props (for examplekey
) The most interesting part for us isPropsWithoutRef<P>
. Here’s it’s realization:What TS does here is:
ref
string a valid property ofP
type ('ref' extends keyof P
)P
, exceptref
(Pick<P, Exclude<keyof P, 'ref'>>
)P
type without changesAs long as
ref
is a string, it’s valid value for index type{[key: string]: unknown}
, therefore in our casePropsWithoutRef<P>
is the same withPick<P, Exclude<keyof P, 'ref'>>
. Furthermore, as long as index type doesn’t have explicit declaration ofref
field,Pick<P, Exclude<keyof P, 'ref'>>
is the same withPick<P, keyof P>
And here’s a punchline:
keyof
operator handles types/interfaces with index signature differently that it handles types/interfaces without index signature. Based on TS docs:In our case it means that for
interface Props { x: string, y: string }
expressionPick<Props, keyof Props>
will returnProps
interface without changes, while forinterface Props { x: string, y: string, [key: string]: unknown }
the same expression will return{[key: string]: unknown}
So suggested workaround from the beginning of this answer fixes this issue. Iit simply copies return type from source code and replaces this:
ForwardRefExoticComponent<Pick<ButtonProps, keyof ButtonProps> & RefAttributes<HTMLButtonElement>>
…with this:ForwardRefExoticComponent<ButtonProps & RefAttributes<HTMLButtonElement>>
But it anyway looks like dirty hack, going to create an issue in
@types/react
repo (if there’s no such)