How to use `React.forwardRef` & `React.memo` in TypeScript with Namespace pattern
For example you have the following working code:
export function Flex(props : FlexProps) : JSX.Element { ... }
function FlexRow(props : FlexRowProps) : JSX.Element { ... }
Flex.Row = FlexRow // !!! works
But it immediately breaks if you add memo or forwardRef:
export const Flex = React.forwardRef(function Flex(props : FlexProps, ref : any)) : JSX.Element { ... })
function FlexRow(props : FlexRowProps) : JSX.Element {}
Flex.Row = FlexRow // !!! Error: property `Row` does not exist on ...
React developers have been struggling with this problem for years. And the proposed solutions are insanely verbose:
export interface Props = {
...yourPropsHere;
};
export interface CompoundedComponent extends React.ForwardRefExoticComponent<Props & React.RefAttributes<HTMLInputElement>> {
yourStaticFunctionOrSomethingLikeThat: () => void;
}
const Component = React.forwardRef<HTMLInputElement, Props>((props, ref) => (
<input ref={ref} {...props} />
)) as CompoundedComponent;
Component.yourStaticFunctionOrSomethingLikeThat = () => {};
Total mess:
type WithStatics<
C extends React.ComponentType<any>,
StaticCmps extends {
[attachedComponents: string]: React.ComponentType<any>;
} = {}
> = C & StaticCmps;
type GridProps = {};
type CellProps = {};
const Cell: React.FunctionComponent<CellProps> = props => <div {...props} />;
const Grid: WithStatics<
React.FunctionComponent<GridProps>,
{ Cell: typeof Cell }
> = props => <div {...props} />;
Grid.defaultProps = { color: "red" };
Grid.Cell = Cell;
Recently, I have found a much easier way to achieve the same result:
tsx
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.