Skip to content

Conditional Typing in TypeScript

Posted on:June 11, 2023

Consider a scenario where you have a parent component rendering a child component, and you want to conditionally pass TypeScript properties based on certain criteria. For instance, let’s say you have a parent component passing data to a child component, and you want to enforce certain properties based on conditions.

Table of contents

Open Table of contents

Conditional Types and Type Guards

Consider a scenario where you have a parent component rendering a child component, and you want to conditionally pass TypeScript properties based on certain criteria. For instance, let’s say you have a parent component passing data to a child component, and you want to enforce certain properties based on conditions.

You might’ve seen code like this.

type CarProps = {
  brand: string;
  engine: "diesel" | "electric";
  fuelEfficiency?: number;
  batteryLife?: number;
};

And this Optional Properties might become a problem.

Let’s adapt the TypeScript example to use a CarProps scenario where the child component receives properties based on whether the car is powered by diesel or electricity.

// Define conditional types for male and female properties
type DieselProps = {
  engine: "diesel";
  fuelEfficiency: number;
};

type ElectricProps = {
  engine: "electric";
  batteryLife: number;
};

// Create a conditional type for the child component
type CarProps = {
  brand: string;
} & (DieselProps | ElectricProps);

We have EngineProps representing the conditional properties based on the car’s engine type, either ‘diesel’ or ‘electric’. The CarProps type combines the mandatory ‘brand’ property with the conditional EngineProps. The CarComponent then uses these types in its props definition.

// Parent component rendering the child
const ParentComponent = () => {
  // Example usage for a male
  const electricProps: CarProps = {
    brand: "Tesla"
    engine: "electric"
    batteryLife: 300
  };

  // Example usage for a female
  const dieselProps: CarProps = {
    brand: "VW"
    engine: "diesel"
    fuelEfficiency: 6
  };

  return (
    <>
      <ChildComponent {...electricProps} />
      <ChildComponent {...dieselProps} />
    </>
  );
};

// Child component receiving conditional props
const ChildComponent = (props: CarProps) => {
  // Access properties based on gender using type guards
  if (props.engine === 'electric') {
    console.log(props.batteryLife); // TypeScript infers type here
  }

  if (props.engine === 'diesel') {
    console.log(props.fuelEfficiency); // TypeScript infers type here
  }

  // Accessing the always-present property
  console.log(props.brand);

  return <div>{/* Component rendering logic */}</div>;
};

In this parent component example, we provide values for ‘brand’, ‘engine’, and ‘batteryLife’. TypeScript ensures that when ‘engine’ is ‘electric’, only the ‘batteryLife’ property is accessible, and any attempt to pass ‘fuelEfficiency’ would result in a type error.

Can I just use extends?

Sure. And your TypeScript code would look like this and the rest of the code would be the same.

type BaseProps = {
  brand: string;
};

// Define types for male and female properties extending the base
type DieselProps = BaseProps & {
  engine: "diesel";
  fuelEfficiency: number;
};

type ElectricProps = BaseProps & {
  engine: "electric";
  batteryLife: number;
};

Choosing the Right Approach

Let’s compare the two approaches:

Approach 1: Conditional Types

Pros:

Cons:

Approach 2: Type Extends

Pros:

Cons:

Conclusion

The choice between these approaches depends on the specific requirements of the project and the nature of the conditions you are dealing with. If your use case involves dynamic conditions and runtime values, the conditional types and type guards approach might be more suitable. If you’re focused on creating modular and reusable types with static conditions, the type extends approach could be a better fit.

In many cases, a combination of both approaches might be used in a project, depending on the context and the complexity of the typing requirements. It’s essential to evaluate the readability, maintainability, and performance implications when deciding on the approach to take.