After dropping quite some time fighting with typings and forwardRefs in React and TypeScript, I am hoping somebody else can clear this up for me.
I am building a DataList component which is comprised of three pieces:
ul
li
Ultimately, the usage would look like this:
<DataList.List ...>
<DataList.Item ...>content</DataList.Item>
</DataList.List>
To achieve the functionality I need, I am leveraging React.forwardRef, but have found this difficult with TypeScript as I struggle correctly typing the components to receive a ref
as well as children
and additional props.
DataListContainer
I essentially want this component to just handle logic and event listeners while returning the primary DataList component (shown next). But the key here, is that I need to create the DataList ref here and assign it to the returned component.
const DataListContainer: React.FC = ({ children }) => {
const listRef = useRef<HTMLUListElement>(null);
return (
<DataList ref={listRef}>{children}</DataList>
);
};
DataList
I want the DataList component to just handle its UI and props.
interface Props extends TestId {
focusedItemId?: string;
}
const DataList: React.FC<Props> = React.forwardRef<HTMLUListElement, Props>(
({ children, focusedItemId, testId }, ref) => {
return (
<ul
aria-activedescendant={focusedItemId}
data-test-id={testId}
ref={ref}
>
{children}
</ul>
);
},
);
This isn't quite right, though. As This setup gives me the error
Type '{ children: any[]; ref: RefObject<HTMLUListElement>; }' is not assignable to type 'IntrinsicAttributes & Props & { children?: ReactNode; }'.
Property 'ref' does not exist on type 'IntrinsicAttributes & Props & { children?: ReactNode; }'.
You were almost there! Following React TypeScript Cheatsheet the problem is with DataList
props. Adding children prop would fix it
interface Props extends TestId {
children?: ReactNode;
focusedItemId?: string;
}
I have recreated a simple example in this CodeSandbox