Search code examples
typescripttypescript-genericsreact-typescript

Typescript conflict with a generic extending a default type


edit: link to playground https://tsplay.dev/NBJ0zN

i'm creating a hook that is supposed to return a modal component, the hook can take a generic to add some extra typings to the body component i inject inside this hook

i have 2 types that look somewhat like this :

export type DefaultBodyComponentProps= {   onClose: ()=> void; };

the onClose funcion comes from inside the hook

export type ConnectModalConfig<B extends object> = {
  bodyComponent?: React.ComponentType<B & DefaultBodyProps>;
};

the modal component returned by this hook is supposed to take an object called bodyProps with some props i can inject inside the body component

export type ModalProps<B> = {   bodyProps?: B; };

the way i use this hook is like this

const [Modal, modalActions] = useConnectedModal<BodyProps>({
  bodyComponent: Body,
});

and the modal component

<Modal bodyProps={..somePropsOfTypeBodyProps}/>

the modal component inside the hook looks like this

export default function useConnectedModal<B extends object = object>(
  props: ConnectModalConfig<B>,
) {
  const { bodyComponent: BodyComponent = DefaultBodyComponent } = props;
  //logic
  const Modal = (props: ModalProps<B>) => {
    const { bodyProps } = props;
    const onClose = () => {
      //logic
    };
    return (
      <Wrapper>
        <BodyComponent {...bodyProps} onClose={onClose} />{' '}
      </Wrapper>
    );
  };
  return [Modal];
}

but i get this typescript error that says :

Type '{ onClose: () => void; }' is not assignable to type 'B'. '{ onClose: () => void; }' is assignable to the constraint of type 'B', but 'B' could be instantiated with a different subtype of constraint 'object'.

I understand what it means but i can't seem to be able to solve it xD

Any tips ?


Solution

  • In the end i solved it by casting bodyProps as B

    <BodyComponent {...bodyProps as B} onClose={onClose} />
    

    not important to my question but i also ended up wanting bodyProps to stay optional so i did this utility type

    type RequirePropsIfAdditionalPropsExist<T, T2 extends string> = keyof Omit<
      T,
      'onClose'
    > extends never
      ? { [K in T2]?: never }
      : { [K in T2]: Omit<T, 'onClose'> };
    
    
    export type ModalProps<B, F, H> = RequirePropsIfAdditionalPropsExist<
      B,
      'bodyProps'
    > &
      RequirePropsIfAdditionalPropsExist<H, 'headerProps'> &
      RequirePropsIfAdditionalPropsExist<F, 'footerProps'> & {
        onClose?: OnClose;
      };
    

    that way when i call the component if i passed something else in the generic other than onClose ts will give me a warning