1

I want a component to accepts different props based on the value of a given, specific one. Something like the following.

const Button: React.FC<TButton> = ({ href, children, ...rest }) => {
  if (href) {
    return <a href={href} {...rest}>{children}</a>
  }

  return <button {...rest}>{children}</button>
}

type TButton = { href: string & IAnchor } | { href: undefined & IButton }

interface IAnchor extends React.AnchorHTMLAttributes<HTMLAnchorElement> {}
interface IButton extends React.ButtonHTMLAttributes<HTMLButtonElement> {}

Thing is, can't figure how to go through this properly. I mean, it seems the conditions aren't being parsed or interpreted correctly.

If you want to have a closer look at the issue, please refer to this StackBlitz.

2 Answers 2

2

It seems that you essentially try to use discriminated union. But it seems that it is not working with ...rest. So to make it work

  1. Add additional property to both interfaces, say type which will be used as discriminant

    interface IAnchor extends React.DetailedHTMLProps<React.AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement> {
        type: 'anchor'
        href: string
    }
    
    interface IButton extends React.ButtonHTMLAttributes<HTMLButtonElement> {
        type: 'button'
    }
    
  2. Accept porps and than destruct them in condition branch

    const Button: React.FC<TButton> = (props): JSX.Element => {
        if (props.type === 'anchor') {
            const { type, href, children, ...rest } = props;
            return (
                <a href={href} {...rest}>
                    {children}
                </a>
            )
        }
        const { type, children, ...rest } = props;
        return <button {...rest}>{children}</button>
    }
    

See working example

Sign up to request clarification or add additional context in comments.

Comments

1

I took a look at your StackBlitz and was able to approach this using the ts-toolbelt library:

First, let's define your two different possible prop types (all anchor props and all button props), combine them as a strict union and use a type guard to let our React component know when we're using which set of props:

import { Union } from 'ts-toolbelt'

type TButton = Union.Strict<IAnchor | IButton>

interface IAnchor extends React.AnchorHTMLAttributes<HTMLAnchorElement> {}

interface IButton extends React.ButtonHTMLAttributes<HTMLButtonElement> {}

const isAnchor = (props: TButton): props is IAnchor => {
  return props.href !== undefined;
}
const isButton = (props: TButton): props is IButton => {
  return props.type !== undefined
}

Now, let's write our custom component that can either be a button or an anchor:

const Button: React.FC<TButton> = ({ children, ...props }): JSX.Element => {
  if (isAnchor(props)) {
    return (
      <a href={props.href} {...props}>
        {children}
      </a>
    )
  } else if (isButton(props)) {
    return <button {...props}>{children}</button>
  }
}

You can view it working in this StackBlitz.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.