I am having some issue finding a way to achieve what I need to do with TS in a React monorepo. The structure is kind of classic:
...repo related stuff
packages /
package-a
package-b
package-c
...other packages
where:
package-ais a collection of base react component which are used throughout the whole repo. It has no dependencies over the other two packages (of course because of circular dependency).package-bhas a different sets of components which are linked to some specific external libraries. It has a dependency overpackage-a.package-cuses components from bothpackage-aandpackage-b. Depends on bothaandbpackages.
What I am trying to achieve is:
- in
package-aI have a component with alinkComponentprop. This prop is another component which has some specific props. The thing is this component can have a type defined inpackage-aor inpackage-bdepending to the component it is passed as prop.
This is the component (with less props obviously):
import {FC} from "react";
import type {IBaseLinkProps} from 'package-a';
import {BaseLink} from 'package-a';
interface IBreadcrumbsProps<P extends IBaseLinkProps> {
linkComponent?: FC<P>;
}
function Breadcrumbs<P extends IBaseLinkProps>({
linkComponent: LinkComponent = BaseLink
}: IBreadcrumbsProps<P>) {
return (
<LinkComponent to={'/path1'}>{'path1'}</LinkComponent>
);
}
export type {IBreadcrumbsProps};
export {Breadcrumbs};
This is the BaseLink in package-a:
import type {FC, ReactNode} from "react";
interface IBaseLinkProps {
to: string;
children: ReactNode;
}
const BaseLink: FC<IBaseLinkProps> = ({children, to}) => (
<a href={to}>{children}</a>
);
export type {IBaseLinkProps};
export {BaseLink};
and this is the link in package-b:
import type {FC} from "react";
import type {IBaseLinkProps} from 'package-a'
interface IExtendedLinkProps extends Omit<IBaseLinkProps, 'to'> {
to: {
pathname: string;
};
}
const ExtendedLink: FC<IExtendedLinkProps> = ({children, to}) => (
<a href={to.pathname}>{children}</a>
);
export type {IExtendedLinkProps};
export {ExtendedLink};
Now the thing is I need to use this component with both IBaseLinkProps and IExtendedLinkProps without the possibility to define it in the Breadcrumbs component in package-a because of circular dependency with package-b.
Is there a way to achieve this without getting the error:
TS2322: Type FC<IBaseLinkProps> is not assignable to type FC<T>
Types of property propTypes are incompatible.
Type WeakValidationMap<IBaseLinkProps> | undefined is not assignable to type WeakValidationMap<T> | undefined
Type WeakValidationMap<IBaseLinkProps> is not assignable to type WeakValidationMap<T>
or:
TS2322: Type { children: ReactNode; to: string; } is not assignable to type T
{ children: ReactNode; to: string; } is assignable to the constraint of type T , but T could be instantiated with a different subtype of constraint IBaseLinkProps
The closest working solution I found is something like:
<Breadcrumbs<IExtendedLinkProps> linkComponent={Link}/>
where the linkComponent in the props is defined as: linkComponent?: FC<T>; and destructured in the props as: linkElement: LinkElement = Link.
But it gives:
TS2344: Type IExtendedLinkProps does not satisfy the constraint IBaseLinkProps
Types of property to are incompatible.
Type {to: {pathname: string}} is not assignable to type string
Update#1
My ExtendedLink component must extend the BaseLink and the RouterLinkProps which comes from react-router:
interface INavLinkProps
extends Omit<IBaseLinkProps, 'to'>,
Omit<RouterLinkProps, 'to'> {
children: ReactNode;
to: IDestToken;
}
const Link: FC<INavLinkProps> = ({children, to}) => {
const {getPathTo} = useNavigation();
const path = useMemo(() => getPathTo(to), [getPathTo, to]);
if (!path) {
throw new Error(`Could not find path for destination: ${to.KEY}`);
}
return (
<RouterLink to={path}>
{children}
</RouterLink>
)
}
so it cannot have the to to be a string because it would mean to add additional type checking also in other places. Furthermore if I try I receive (consider that IDestToken is an object similar to the one I originally posted): TSError
Your issue here is that
IExtendedLinkProps.to({ pathname: string }) is not compatible withIBaseLinkProps.to(string)Which is why you cannot use
BreadcrumbswithExtendedLinkYour breadcrumbs render method:
There would obviously be an issue if you used
ExtendedLinkas it expect an object and not a stringOne way to make it compatible would be to make
ExtendedLinkaccept both string and object