Loading...

Postulate is the best way to take and share notes for classes, research, and other learning.

More info

How to Require "One Set of Properties or the Other" In Typescript

Profile picture of Samson ZhangSamson Zhang
Mar 16, 20212 min read

A common task in Typescript is specifying types for props of a component. Usually, this is quite straightforward:

export default function Person(props: {name: string, age: number}) { return ( ... ); }

However, it's not uncommon for a component to have two possible sets of valid props. For Postulate, for example, I use the same CommentItem component for both new and existing comments, as the editor code and state variables are shared. Props for this component can be either just comment, an object containing all of the comment's information, or authorId, targetId, and parentCommentId, separate props containing the information needed to create a new comment.

I can implement this by making all props optional:

export default function CommentItem(props: {comment?: CommentObj, authorId?: string, targetId?: string, parentCommentId?: string}) { return ( ... ); }

However, now I can create a CommentItem with no props, or with two of the three props required for a new comment, and Typescript won't know that something is wrong. To type this component properly, we need to create a type that somehow takes one or the other set of properties.

We'll use something you've likely already used before to make this happen: unions! If that sounds unfamiliar, here's what they look like:

type StringOrNumber = string | number

We can simply create a more complex union type:

interface PropsNew { authorId: any, targetId: string, parentCommentId: string, } interface PropsExisting { comment: CommentObj, } type Props = PropsNew | PropsExisting;

But this is not yet enough. To ensure that you can have either one set of props or the other, but not, say, all four props, we have to specify never types for unwanted props in each of the interfaces. Here's what that looks like:

interface PropsNew { authorId: any, targetId: string, parentCommentId: string, comment?: never, } interface PropsExisting { authorId?: never, targetId?: never, parentCommentId?: never, comment: CommentObj, } type Props = PropsNew | PropsExisting;

And as a last touch, we can have both options extend a base props interface to add props common to both types:

interface PropsBase { iteration: number, setIteration: Dispatch<SetStateAction<number>>, } interface PropsNew extends PropsBase { authorId: any, targetId: string, parentCommentId: string, comment?: never, } interface PropsExisting extends PropsBase { authorId?: never, targetId?: never, parentCommentId?: never, comment: CommentObj, } type Props = PropsNew | PropsExisting;

Bam, we can now specify either one set of props or another for our component!

You can apply the same approach to functions, object variables, or anything else you need to type.

I originally found this solution on


Comments (loading...)

Sign in to comment

Postulate

Founder and dev notes from building Postulate