I am trying to figure out the types for Dot Notation components with forwardRef.
So I found this example which perfectly illustrates how I currently use dot notation, but doesn't include forwardRef: https://codesandbox.io/s/stpkm
This is what I'm trying to achieve, but can't figure out the typings.
import { forwardRef, useImperativeHandle } from "react";
//
import { ForwardRefRenderFunction } from "react";
const TabToggle: React.FC = () => null;
const TabContent: React.FC = () => null;
interface TabsStatic {
Toggle: typeof TabToggle;
Content: typeof TabContent;
}
export interface TabsProps {
initialIndex?: number;
}
export interface TabsRefMethods {
show: () => null;
hide: () => null;
}
export const Tabs: React.ForwardRefRenderFunction<
TabsRefMethods,
TabsProps & TabsStatic
> = forwardRef((props, ref) => {
const openFn = () => null;
const closeFn = () => null;
useImperativeHandle(ref, () => ({ show: openFn, hide: closeFn }));
return null;
});
Tabs.Toggle = TabToggle;
Tabs.Content = TabContent;
Code Sandbox Demo : https://codesandbox.io/s/jsx-dot-notation-in-typescript-with-react-forked-38e1z?file=/src/Tabs.tsx
There are two possible approaches I'm aware of and have used. Both have slightly different tradeoffs.
The first is to interset ( &
) the static components and mark them as optional ( Partial
) so that there isn't an error when declaring the component. The downside is that they are denoted as optional even though they are always set.
import * as React from "react";
const TabToggle: React.FC = () => null;
const TabContent: React.FC = () => null;
interface TabsStatic {
Toggle: typeof TabToggle;
Content: typeof TabContent;
}
export interface TabsProps {
initialIndex?: number;
}
export interface TabsRefMethods {
show: () => null;
hide: () => null;
}
export const Tabs: React.ForwardRefExoticComponent<
React.PropsWithoutRef<TabsProps> & React.RefAttributes<TabsRefMethods>
> &
Partial<TabsStatic> = React.forwardRef((props, ref) => null);
Tabs.Toggle = TabToggle;
Tabs.Content = TabContent;
The alternative is to make them required, but this requires a cast. The resulting type is more accurate, but it does require a cast.
type TabsComponent = React.ForwardRefExoticComponent<
React.PropsWithoutRef<TabsProps> & React.RefAttributes<TabsRefMethods>
> &
TabsStatic;
export const Tabs = React.forwardRef((props, ref) => null) as TabsComponent;
Tabs.Toggle = TabToggle;
Tabs.Content = TabContent;
There is much easier way to achieve the same result:
const Flex = React.forwardRef(function Flex(...) {
...
})
const FlexRow = React.forwardRef(...)
const FlexColumn = React.forwardRef(...)
const FlexNamespace = Object.assign(Flex, {Row: FlexRow, Column: FlexColumn})
export {FlexNamespace as Flex}
Now you can use Flex
, Flex.Row
, and Flex.Column
with TS being happy. The magic line is Object.assign
which does not cause the same type issue as Flex.Row = FlexRow
does. Don't ask me why, I just accidentally found this
Or even shorter:
const TabsComponent = () => {
// implement the Tabs component
};
const TabContentComponent = () => {
// implement the TabContent component
};
const TabToggleComponent = () => {
// implement the TabToggle component
};
export const Tabs = Object.assign(forwardRef(TabsComponent),
// a component with forward refs
TabContent: forwardRef(TabContentComponent),
// a component without forwarding
TabToggle
};
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.