Here is a simplified version of a Tabs
component that exposes a method called activateTab
through useImperativeHandle
:
type TabsProps<TabName> = {
tabs: readonly { name: TabName }[];
children: ReactNode;
};
const TabsComponent = <TabName extends string>(
props: TabsProps<TabName>,
ref: Ref<{ activateTab: (tabName: TabName) => void }>
) => {
const { tabs, children } = props;
useImperativeHandle(ref, () => ({
activateTab: (tabName: TabName) => {}
}));
return (
<div>
<div role="tablist">
{tabs.map(({ name }) => (
<button type="button" role="tab" key={name}>
{name}
</button>
))}
</div>
{children}
</div>
);
};
const Tabs = forwardRef(TabsComponent);
// Usage:
const tabs = [{ name: "Tab 1" }, { name: "Tab 2" }] as const;
export default function App() {
return (
<Tabs tabs={tabs}>
{...}
</Tabs>
);
}
All is good until this point. Here is a working CodeSandbox.
But, now I want to add a Tabs.Panel
component so the usage is:
export default function App() {
return (
<Tabs tabs={tabs}>
<Tabs.Panel>Content</Tabs.Panel>
</Tabs>
);
}
I tried the following, but TypeScript complains:
type PanelProps = {
children: ReactNode;
};
const Panel = ({ children }: PanelProps) => {
return <div role="tabpanel">{children}</div>;
};
Tabs.Panel = Panel;
~~~~~
^
Property 'Panel' does not exist on type 'ForwardRefExoticComponent<TabsProps<string> & RefAttributes<{ activateTab: (tabName: string) => void; }>>'
What's the best way to achieve this Tabs.Panel
API in TypeScript?
You need to use Object.assign
:
import React, { forwardRef, ReactNode, Ref, useImperativeHandle, FC } from "react";
type PanelProps = {
children: ReactNode;
};
const Panel = ({ children }: PanelProps) => {
return <div role="tabpanel">{children}</div>;
};
type TabsProps<TabName> = {
tabs: readonly { name: TabName }[];
children: ReactNode;
};
const TabsComponent = <TabName extends string>(
props: TabsProps<TabName>,
ref: Ref<{ activateTab: (tabName: TabName) => void }>
) => {
const { tabs, children } = props;
useImperativeHandle(ref, () => ({
activateTab: (tabName: TabName) => { }
}));
return (
<div>
<div role="tablist">
{tabs.map(({ name }) => (
<button type="button" role="tab" key={name}>
{name}
</button>
))}
</div>
{children}
</div>
);
};
const Tabs = Object.assign(forwardRef(TabsComponent), { Panel });
const tabs = [{ name: "Tab 1" }, { name: "Tab 2" }] as const;
export default function App() {
return <Tabs tabs={tabs}>
<Tabs.Panel>Content</Tabs.Panel> // ok
</Tabs>;
}
Here you can find more explanation about using static properties on functions