Search code examples
javascriptreactjstypescriptstyled-components

Type 'string' is not assignable to type 'undefined'. TS2769


I am adding some conditional styling depending on the the href type. For some reason if I add my as to the Button styled-component I get the TS error: Type 'string' is not assignable to type 'undefined'. TS2769

Component

interface LinkProps {
  to: string;
  target?: string;
  as?: string;
  className?: string;
  children: ReactChildren | React.ReactNode | string;
}

const Button = styled(RLink)`
  text-decoration: none;
`;

export const Link = ({
  to,
  target,
  children,
  as = 'a',
  className
}: LinkProps) => {
  return (
    <Button
      as={as}
      to={to}
      target={target}
      rel="noopener"
      className={className}
    >
      {children}
    </Button>
  );
};

Without the as:

enter image description here

WIthout the as:

enter image description here

Full TS Error

index.js:1 /frontend/claim-handler/src/components/shared/Link/index.tsx
TypeScript error in /frontend/claim-handler/src/components/shared/Link/index.tsx(39,6):
This JSX tag's 'children' prop expects type 'never' which requires multiple children, but only a single child was provided.  TS2745

37 | }: LinkProps) => {
  38 |   return (
    > 39 |     <Button
           |      ^
40 |       as={as}
        41 |       to={to}
        42 |       target={target}
console.<computed> @ index.js:1
handleErrors @ webpackHotDevClient.js:174
push.../../node_modules/react-dev-utils/webpackHotDevClient.js.connection.onmessage @ webpackHotDevClient.js:213


index.js:1 /frontend/claim-handler/src/components/shared/Link/index.tsx
TypeScript error in /frontend/claim-handler/src/components/shared/Link/index.tsx(40,7):
No overload matches this call.
Overload 1 of 2, '(props: Omit<Omit<import("/node_modules/@types/react-router-dom/index").LinkProps<unknown> & RefAttributes<HTMLAnchorElement> & LinkProps, never> & Partial<...>, "theme"> & { ...; } & { ...; }): ReactElement<...>', gave the following error.
  Type 'string' is not assignable to type 'undefined'.  TS2769

  38 |   return (
    39 |     <Button
    > 40 |       as={as}
      |       ^
      41 |       to={to}
      42 |       target={target}
      43 |       rel="noopener"

Edit

By removing the option as, which I don't agree with, as aren't these Types what the component expects to recieve as Props, not just arguments?

Here are the new errors:

enter image description here


Solution

  • The main problem is you're trying to pass extremely wide type string to the prop accepting quite a big but still exactly defined set of strings as names of native html tags. If you're going to pass as as prop only the names of html tags (i.e. not other functional or class components) you may type as like that:

    interface LinkProps {
      to: string;
      target?: string;
      as?: keyof JSX.IntrinsicElements;
      className?: string;
      children: ReactChildren | React.ReactNode | string;
    }
    

    That should solve your problem.

    And another minor issue. While it's completely ok to type children prop yourself. That's pretty brittle way. And you have already got it wrong. ReactChildren is not a type of array of react children. That's a type of React.Children interface combining utility functions to work with children prop.

    For typing children prop react exposes special helper type React.PropsWithChildren. And if you wish you may rewrite your code in more stable way:

    import * as React from 'react'
    import styled from 'styled-components'
    
    interface LinkProps {
        to: string;
        target?: string;
        as?: keyof JSX.IntrinsicElements;
        className?: string;
    }
    
    const Button = styled(RLink)`
        text-decoration: none;
    `;
    
    export const Link = ({
        to,
        target,
        children,
        as = 'a',
        className
    }: React.PropsWithChildren<LinkProps>) => {
        return (
            <Button
                as={as}
                to={to}
                target={target}
                rel="noopener"
                className={className}
            >
                {children}
            </Button>
        );
    };