Pooria Khodaveissi
Pooria Khodaveissi

Reputation: 395

React Props with TS Conditional Types not working as expected

I have a conditional type within a component like so:

type Props =
  | {
      isValueNumeric: true;
      value: number;
    }
  | {
      isValueNumeric?: false;
      value: string;
    };

And a parent with the same comps too, but when you pass the props to the child Comp I get:

Type '{ value: string | number; isValueNumeric: boolean | undefined; }' is not assignable to type 'IntrinsicAttributes & PropsWithChildren<TSurfaceSymbolProps>'.
  Type '{ value: string | number; isValueNumeric: boolean | undefined; }' is not assignable to type '{ isValueNumeric?: false | undefined; value: string; }'.
    Types of property 'isValueNumeric' are incompatible.
      Type 'boolean | undefined' is not assignable to type 'false | undefined'.
        Type 'true' is not assignable to type 'false | undefined'.ts(2322)

Please check this sandbox for more info:

https://codesandbox.io/s/beautiful-cookies-yvj4fh App.tsx:21

CHILD COMP:

import React from "react";

type Props =
  | {
      isValueNumeric: true;
      value: number;
    }
  | {
      isValueNumeric?: false;
      value: string;
    };
type TSurfaceSymbolProps = React.HTMLAttributes<HTMLDivElement> & Props;

const SurfaceSymbol: React.FC<TSurfaceSymbolProps> = ({
  value,
  isValueNumeric,
  ...props
}) => {
  let surface;
  if (isValueNumeric === true) {
    surface = parseInt(value, 10); // just for the sake of this example
  } else {
    surface = value;
  }

  return <div>{surface}</div>;
};

export default SurfaceSymbol;

PARENT COMP:

import "./styles.css";
import Comp from "./Comp";

type Props =
  | {
      isValueNumeric: true;
      value: number;
    }
  | {
      isValueNumeric?: false;
      value: string;
    };
interface IAppProps extends React.HTMLAttributes<HTMLDivElement> {
  secondary?: boolean;
}

type TAppProps = IAppProps & Props;
const App: React.FC<TAppProps> = ({ value, isValueNumeric }) => {
  return (
    <div className="App">
      <Comp value={value} isValueNumeric={isValueNumeric} />
      <h1>Hello CodeSandbox</h1>
      <h2>Start editing to see some magic happen!</h2>
    </div>
  );
};

export default App;

Upvotes: 0

Views: 639

Answers (2)

Dương Đức Anh
Dương Đức Anh

Reputation: 11

Instead of

type Props = BaseProps & ConditionProps

const Component: React.FC<Props> = ({ ... })

Write like this

type BaseProps =  {...}
type ConditionProps =  {...}

const Component: React.FC<BaseProps & ConditionProps> = ({ ... })

It will work as your expected

Upvotes: 0

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 249636

If you do not destructure the union then everything will work:

const App: React.FC<Props> = (props) => {
  return (
    <div className="App">
      <SurfaceSymbol {...props} /> 
      <h1>Hello CodeSandbox</h1>
      <h2>Start editing to see some magic happen!</h2>
    </div>
  );
};

Playground Link

Once you destructure the fields from the object typescript is not able to rebuild the original union when you assign the props (it is able to narrow them since TS 4.6)

Upvotes: 1

Related Questions