Conditional Properties Using React With TypeScript

In this article, we are going to see about the Typescript conditional properties with react, which means props that depend on the other props.

Let’s see an example before we hop in,

<MathObject objectName=”Square” objectType=”square” width={22} />
<MathObject objectName=”Rectangle” objectType=”rectangle” width={22} height={33}/>
<MathObject objectName=”Circle” objectType=”circle” radius={5} />

In the above code, you can notice based on the objectType value the number of properties and property types become change.

Conditions

  • If the objectType is square then the width is the mandatory field,
  • if the objectType is a rectangle then width and height is a mandatory field,
  • if the objectType is a circle then the radius is mandatory, no need for width and height.

So, what is a conditional property here, whatever the value of objectType having, makes an impact on the other properties that we might receive in that component?

Let’s see, how we achieved this conditional props concept in the MathObject component. I hope you are very familiar with interfaces and types in typescript.

Interface objectProps{
	objectType: “square”|”rectangle”|”circle”;
	Width?: number;
	Height?: number;
	Radius?: number;
}
Export function MathObject(props : objectProps){
	Return <pre>{JSON.stringify(props)}</pre>
}

This is how programmers will start at the first time, initially, we’ll create an interface like the above one. So, what we have in that objectProps interface, we have an objectType that can be either square or rectangle or circle, then we have the width, height, and radius as optional properties.

The above example doesn’t satisfy the conditional props concept, because, the problem with this approach is, objectType is a mandatory field, but other values are optional. If we do not declare width, height, or radius, it doesn’t return any error even if we call that component like below,

<MathObject objectName=”Circle” objectType=”circle” radius={5} width={22} height={33}/>

This approach is entirely wrong, because if the objectType is a circle, then the only mandatory field for a circle is the radius, not width or height.

So, how to overcome this, let's create a separate interface for all object types,

Interface SquareProps {
    objectType: “square”;
    width: number;
}
Interface RectangleProps {
    objectType: “rectangle”;
    width: number;
    height: number;
}
Interface CircleProps {
    objectType: “circle”;
    radius: number;
}
Export
function MathObject(props: SquareProps | RectangleProps | CircleProps) {
    Return < pre > {JSON.stringify(props)} < /pre>
}

If we do it like the above way, we have all benefits of the conditional props concept. If we pass objectType as the square it’ll accept only width props, for rectangle it’ll accept both width and height, for radius it’ll accept only radius props.

It’s not finished yet., did you noticed at the beginning in the MathObject component calling, we have used objectName props,

<MathObject objectName=”Square” objectType=”square” width={22} />

Here the objectName prop is common for all objectType. But in non of the interface have objectName property. So how to achieve it, usually we have two options, one – we have to declare objectName in every interface like below,

Interface SquareProps {
    objectName: ”string”;
    objectType: “square”;
    width: number;
}
Interface RectangleProps {
    objectName: ”string”;
    objectType: “rectangle”;
    width: number;
    height: number;
}
Interface CircleProps {
    objectName: ”string”;
    objectType: “circle”;
    radius: number;
}

The above one will work, but imagine that we have n-number of common properties or n-number of interfaces like that,

In the 2nd option, we can create one common interface, and we can extend that with interfaces like below,

Interface ObjectName {
    objectName: ”string”;
}
Interface SquareProps extends ObjectName {
    objectType: “square”;
    width: number;
}

But, these two options also not perfect, because of a lot of duplicate codes and a lot of code change, probably not much benefit.

So, how can we achieve that? By merging common properties with all interfaces, like below,

Export type MathObjectProps = { objectName:”string”;} & ( SquareProps | RectangleProps | CircleProps);
Export function MathObject(props : MathObjectProps){
	Return <pre>{JSON.stringify(props)}</pre>
}

That’s it, the above code will merge the objectName property with other all interfaces. But how it becomes possible. You can be noticed that we are using the ampersand(&) symbol, it merging the property objectName type with whatever the interface comes after.

Now the objectName is a valid property for the objectType - “square”, “rectangle”, “circle”. This is awesome, right.,

if you have n-number of common properties, we can create one common interface and merge that one with other interface’s,

interface commonProperties {
objectName:”string”;
}
Export type MathObjectProps = (commonProperties)& ( SquareProps | RectangleProps | CircleProps);
Export function MathObject(props : MathObjectProps){
	Return <pre>{JSON.stringify(props)}</pre>
}

In some internet examples, most the peoples will use like below,

Export type MathObjectProps = { objectName:”string”;} & (
{ objectType: “square”; width: number; }
| { objectType: “rectangle”; width: number; height: number; }
| { objectType: “circle”; radius: number; }
);
Export function MathObject(props : MathObjectProps){
	Return <pre>{JSON.stringify(props)}</pre>
}

When I saw this type of code, the first time it was a bit scary. I hope it won’t scare you anymore.

Summary

In this article, we have seen conditional props in typescript and different ways to use them. I hope this article will help you understand the conditional props in typescript.js.