Home > Back-end >  TypeScript and reusable components in React
TypeScript and reusable components in React

Time:07-09

I am trying to create a reusable component that takes one of two different types, let's call them Car and Truck that we get from API.

type Props = {
  data: Car | Truck;
  onToggleDataSave: (id: number, isHome: boolean) => void;
  isCar: boolean;
};

const Card = ({ data, onToggleDataSave, isCar }: Props) => {
  const handleToggleCardSave = (e: SyntheticEvent) => {
    e.preventDefault();
    isCar
      ? onToggleDataSave(data.carId, isCar)
      : onToggleDataSave(data.truckId, isCar);
  };


  return (
    <h1> Some markup </h1>
  );
};

This works in JavaScript as I am making a check if my data is a car before I try to access its property, but TS isn't happy about it as it says that Truck doesn't have carId property.

How can I satisfy TypeScript while keeping my component reusable?

CodePudding user response:

This doesn't require any additional checks, just better types.

type Car = { carId: number }
type Truck = { truckId: number }

type PropsInCommon = {
  onToggleDataSave: (id: number, isHome: boolean) => void;
}

type Props = ({
  data: Car;
  isCar: true;
} | { 
  data: Truck;
  isCar: false
}) & PropsInCommon;

const Card = ({ data, onToggleDataSave, isCar }: Props) => {
  const handleToggleCardSave = (e: SyntheticEvent) => {
    e.preventDefault();
    isCar
      ? onToggleDataSave(data.carId, isCar)
      : onToggleDataSave(data.truckId, isCar);
  };


  return (
    <h1> Some markup </h1>
  );
};

Even though this may feel a little roundabout, it feels like it expresses your intent without requiring a type guard.

This code essentially tells the compiler that the isCar: true, data: Truck state is not valid using the union operator.

This better informs the compiler the expected values of this type:

When isCar is true, data has type Car.

CodePudding user response:

type Props = {
    data: Car | Truck;
    onToggleDataSave: (id: number, isHome: boolean) => void;
    isCar: boolean;
};

function carGuard(_data: Car | Truck, isCar: boolean): _data is Car {
    return isCar;
}

export const Card = ({ data, onToggleDataSave, isCar }: Props): JSX.Element => {
    const handleToggleCardSave = (e: SyntheticEvent): void => {
        e.preventDefault();
        if (carGuard(data, isCar)) {
            onToggleDataSave(data.carId, isCar);
        } else {
            onToggleDataSave(data.truckId, isCar);
        }
    };

    return <h1> Some markup </h1>;
};

CodePudding user response:

The other answers looks good, but do you really need the isCar props that you are passing to your component. You can decide if it is a car or not by checking the type like this:

    type Car = {
      carId: number;
    };
    
    type Truck = {
      truckId: number;
    };
    
    interface Props {
      vehicle: Car | Truck;
    }


    const isCar = (v: any): v is Car => {
      return v.carId !== undefined;
    };
    
    const Vehicle = (props: Props) => {
    
      return (
        <div>
          {isCar(props.vehicle) ? props.vehicle.carId : props.vehicle.truckId}
        </div>
      );
    };
    
    export default function App() {
      const vehicle = { carId: 1 } as Car;
    
      return (
        <div className="App">
          <h1>Vehicle</h1>
          <Vehicle vehicle={vehicle} />
        </div>
    }
  • Related