Reputation: 2644
I have a Panel component that optionally allows the user to use compound components. The panel has a default header that contains the close functionality (button) and the main reason I did this is was to give the user the option to use a custom header with their own button and whatever else they would like, so I opted to use compound components and to allow the Panel component to check for the existence of the component in its children and if so, it will not use the default header. I have it all working but I can't seem to figure out how to get the Typescript error to clear.
Below is the code pertaining to Typescript and the error I'm getting. I also linked to a sandbox with the working code with errors.
I have tried tons of variations from Google searches and in a days time cannot find the solution.
Sandbox: https://codesandbox.io/s/panel-513yq?file=/src/Panel/Panel.tsx:510-4061
App.js:
export default function App() {
const [open, setOpen] = React.useState(false);
const handleClose = () => {
setOpen(false);
};
const handleOpen = () => {
setOpen(true);
};
return (
<div id="page" data-test-id="component-app">
<header id="header">
<button onClick={handleOpen}>Open</button>
</header>
<main id="main">
<Panel open={open} onClosed={handleClose} renderPortal={true}>
<Panel.Header>
<div>
<button onClick={handleClose}>Close</button>
</div>
</Panel.Header>
<Panel.Content>Panel Content</Panel.Content>
</Panel>
</main>
</div>
);
}
Panel Component:
interface ModalPropsComposition {
Header?: React.FC<PanelHeaderProps>;
Content?: React.FC<PanelContentProps>;
}
export interface PanelProps
extends React.HTMLAttributes<HTMLDivElement>,
ModalPropsComposition {
open: boolean;
position?: string;
renderPortal?: boolean;
// Lifecycle callbacks
onClose?: () => void;
onClosed?: () => void;
onOpen?: () => void;
onOpened?: () => void;
}
/**
* @Codeannex UI React: Panel Component
*
* Panel Component
*/
const _Panel = ({
children,
open,
renderPortal,
onClose,
onClosed,
onOpen,
onOpened
}: PanelProps & { forwardedRef: React.Ref<HTMLDivElement> }): JSX.Element => {
// panel code...
};
export const Panel = React.forwardRef(
(props: PanelProps, ref: React.Ref<HTMLDivElement>): JSX.Element => {
return <_Panel {...props} forwardedRef={ref} data-testid={PANEL_TEST_ID} />;
}
);
Panel.Header = PanelHeader;
Panel.Content = PanelContent;
Error:
Upvotes: 6
Views: 5500
Reputation: 1984
By doing in Panel.tsx
Panel.Content = PanelContent;
Panel.Header = PanelHeader
typescript complains since the properties Content and Header are custom and don't exist on the base forwardRef type.
To fix it declare a custom interface that extends forwardRef base type and add your custom properties Header and Content:
interface IPanel
extends React.ForwardRefExoticComponent<
PanelProps & React.RefAttributes<HTMLDivElement>
> {
Header: typeof PanelHeader;
Content: typeof PanelContent;
}
and use it like this:
const forwardRef = React.forwardRef<HTMLDivElement, PanelProps>(
(props, ref): JSX.Element => {
return (
<PanelComponent
{...props}
forwardedRef={ref}
data-testid={PANEL_TEST_ID}
/>
);
}
);
export const Panel = {
...forwardRef,
Header: PanelHeader,
Content: PanelContent
} as IPanel;
Or... just do export const Panel: any
but you lose type checking by doing this
Working CodeSandbox forked from yours.
Upvotes: 10
Reputation: 118741
You can fix this error by using Object.assign
. This works because Object.assign is typed as assign<T, U>(target: T, source: U): T & U;
, so the types T and U can be inferred, and the return type becomes a mix of the forwardRef exotic component type and the object with Header
and Content
properties.
export const Panel = Object.assign(
React.forwardRef(
(props: PanelProps, ref: React.Ref<HTMLDivElement>): JSX.Element => {
return (
<PanelComponent
{...props}
forwardedRef={ref}
data-testid={PANEL_TEST_ID}
/>
);
}
),
{
Header: PanelHeader,
Content: PanelContent
}
);
Upvotes: 12