最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Getting TypeScript to Infer Props Correctly for a Nested Generic Function Component - Stack Overflow

programmeradmin2浏览0评论

The title will make more sense with an example:

I have the following component GlobalDialog:

export type GlobalDialogProps<T extends React.FunctionComponent<any>> = {
    Trigger: React.FunctionComponent<{ onToggle: () => void; }>;
    Panel: T;
    panelProps: ComponentProps<T>;
    zIndex: number;
};

export default function GlobalDialog<T extends React.FunctionComponent<any>>(props: GlobalDialogProps<T>) {
    // Implementation details
}

and component FilterProductResultsControllerV2

export default function FilterProductResultsControllerV2<T extends React.FC<any>>(props: { ResultElement: T; propsFactory: (product: IProduct) => ComponentProps<T>; }) {
    // Implementation details
}

I then try to pass FilterProductResultsControllerV2 as a Panel to my Global Dialog, and propsFactory ends up being inferred as (product: IProduct) => any instead of (product: IProduct) => CountTrackerProductProps

<GlobalDialog
    zIndex={10}
    Trigger={({ onToggle }) => (
        // Implementation detail
    )}
    Panel={FilterProductResultsControllerV2}
    panelProps={{
        renderAs: "panel",
        ResultElement: CountTrackerProduct,
        propsFactory: (product) => {
            const orderItemData = value[product.id];

            return {
                product: product,
                onAdd: () => addOrderItem(product),
                quantity: orderItemData?.quantity ?? null,
            };
        },
    }}
/>

where CountTrackerProduct is:

type CountTrackerProductProps = { product: IProduct; onAdd: () => void; quantity: number | null; };

export default function CountTrackerProduct(props: CountTrackerProductProps) { 
    // Implementation details
}

A working solution to this problem is using FilterProductResultsControllerV2<typeof CountTrackerProduct> as Panel, but it's not the most elegant one either.

So my question is, why exactly does this happen in Typescript? and are there any ways around it, if any?

Full Sample Code that features the issue

import { useState } from "react";
    
interface IProduct {
    id: string;
    name: string;
}

type GlobalDialogProps<T extends React.FunctionComponent<any>> = {
    Trigger: React.FunctionComponent<{ onToggle: () => void }>;
    Panel: T;
    panelProps: React.ComponentProps<T>;
    zIndex: number;
};

function GlobalDialog<T extends React.FunctionComponent<any>>(props: GlobalDialogProps<T>) {
    const [open, setOpen] = useState(false);
    const { Panel, panelProps, Trigger } = props;
    return (
        <>
            <Trigger onToggle={() => setOpen(!open)} />
            {/*<Panel {...panelProps} /> */}
        </>
    );
}

function FilterProductResultsControllerV2<T extends React.FC<any>>(props: { ResultElement: T; propsFactory: (product: IProduct) => React.ComponentProps<T> }) {
    return <div></div>;
}

type CountTrackerProductProps = {
    product: IProduct;
    onAdd: () => void;
    quantity: number | null;
};

function CountTrackerProduct(props: CountTrackerProductProps) {
    return <div></div>;
}

function OrderItemDataWidget() {
    const [value, setValue] = useState<Record<IProduct["id"], { quantity: number }>>({});
    const addOrderItem = (product: IProduct) => console.log(`added product ${product.id}`);

    return (
        <div>
            <div>close off</div>
            <GlobalDialog
                zIndex={10}
                Trigger={({ onToggle }) => <button onClick={onToggle} type="button">dummy</button>}
                Panel={FilterProductResultsControllerV2}
                panelProps={{
                    ResultElement: CountTrackerProduct,
                    propsFactory: (product) => {
                        const orderItemData = value[product.id];

                        return {
                            product: product,
                            onAdd: () => addOrderItem(product),
                            quantity: orderItemData?.quantity ?? null,
                        };
                    },
                }}
            />
        </div>
    );
}

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论