Commonly-Used Props

Let's take a moment to look at some of the types that go along with some of the more common props that we tend to see in React applications.

For starters, we have our basic primitives.

type CounterProps = {
  incident: string;
  count: number;
  enabled: boolean;
};

We can also have arrays or collections of primitives.

type GroceryListProps = {
  items: string[];
};

Sometimes, we don't want to allow any string—only certain strings. We can use a union type to represent this.

type GroceryListProps = {
  items: string[];
  status: "loading" | "error" | "success";
};

It's not uncommon for us to find ourselves using objects in JavaScript (erm, TypeScript). So, what would that look like?

type ContrivedExampleComponmentProps = {
  anObject: object; // Useful as a placeholder.
  anotherObject: {}; // Can have any properties and values.
  item: {
    id: string;
    title: string;
  };
  items: {
    id: string;
    title: string;
  }[]; // An array of objects of a certain shape.
};

We could refactor this a bit. (I know, it's a contrived example, but go along with it.)

type Item = {
  id: string;
  title: string;
};

type ContrivedExampleComponmentProps = {
  item: Item;
  items: Item[];
};

So, if you look at our two object examples above, we're missing something.

  • {} will allow for an object with any keys and any values.
  • { id: string; title: string; } will only allow for an object with two keys: id and title as long as those values are both strings.

But, what if we wanted to find a happy medium? What if we wanted a situation where we said, "Listen, the key can be any string and the value has to be of a certain type.

That might look something like this:

type ItemHash = {
  [key: string]: Item;
};

Or, if we wanted to say the keys are number and the values are strings, it would look like this:

type Dictionary = {
  [key: number]: string;
};

Another way of writing either of those would be as follows:

Record<string, Item>
Record<number, string>

I prefer the first syntax, personally. But, this is your life. You do what you want.

Okay, so we tend to also pass functions around, right? What does that look like?

type ContrivedExampleProps = {
  // Does not take any arguments. Does not return anything.
  onHover: () => void;
  // Takes a number. Returns nothing (e.g. undefined).
  onChange: (id: number) => void;
  // Takes an event that is based on clicking on a button.
  // Returns nothing.
  onClick(event: React.MouseEvent<HTMLButtonElement>): void;
};

A standalone function that you type as your declare it, is a little bit different.

const add = (a: number, b: number): number => {
  return a + b;
};

function subtract(a: number, b: number): number {
  return a - b;
}

Finally, we should consider the fact that not every prop is required.

type ContrivedProps = {
  requiredProp: boolean;
  optionalProp?: string;
};

Your Turn

Start here.

Okay, just to get the blood flowing and build up some muscle memory. Why don't you add a second optional prop: the ability to replace "Hello" with the greeting of your choosing.

You can see the solution here.

Let's experiment a bit.

  • What happens if we omit the property all together?
  • What happens if we try to call a method on the string?
  • What happens if we try to set a default value?

Where Are We Now?

The current state of the code can be found in:

  • examples/03-nametag-with-optional props
  • projects/nametag on the nametag-with-optional-props branch
  • CodeSandbox