Go back

Optional props in TypeScript

I've been getting myself familiar with the game of TypeScript for a while now, by contributing to a particular TypeScript (React|Next.js), and the experience has been well... cool

Some days ago, I improved a custom <Icon /> component I had built, by allowing it to receive props, a name prop. So all you had to do, to use this prop is pass the name of the SVG icon as a value to it.

This is what it looked like:

import React from 'react'
import { ReactSVG } from 'react-svg'
 
interface iconProp {
  name: string
}
 
export const Icon: React.FC<iconProp> = ({ name }) => {
  return <ReactSVG src={`/icons/${name}.svg`} />
}

In the snippet above, I'm extending the prowess of ReactSVG by passing the path to where our icons are located with the name prop appended to it, in the string literal. Hence, using this component becomes like so:

<Icon name="user" />

Requiring more props

But, as time went on, the need to modularize — let me use the word "group" — our icons came up since they are local to a particular UI.

Creating a new folder in the "icons" folder was apparent. So, I did just that, and placed the necessary SVG files in it — Let's call this folder, dashboard-icons.

Modifying <Icon /> to accept a new prop that takes this new icon path as data became the next on my list. At first, I was a bit confused.

But, using the ternary operator saved the day. Take a look at the updated component below:

import React from 'react'
import { ReactSVG } from 'react-svg'
 
interface iconProp {
  name: string
  dashboard: boolean
}
 
export const Icon: React.FC<iconProp> = ({ name, dashboard }) => {
  return (
    <ReactSVG
      src={
        dashboard ? `/icons/dashboard-icons/${name}.svg` : `/icons/${name}.svg`
      }
    />
  )
}

At first, I thought adding boolean as a value to dashboard would solve this issue of it being an optional prop, until I started getting build errors because TypeScript was screaming that the prop, dashboard, is required.

So, I went on to use this component like so:

<Icon name="user" dashboard={false} />

Making some props optional, correctly.

But, this approach of passing dashboard={false} every time I want to use an icon that isn't in the dashboard-icons folder isn't optimal.

Why? Say, for example now, the project grows and we have more icons in their respective modules, We'll now have a component that looks like this"

<Icon
  name="user"
  dashboard={false}
  settings={false}
  profile={false}
  tasks={false}
/>

This sorta destroys the DX — Developer Experience — of the next Dev that'll use this component in your team.

Luckily for me, I remembered the concept of optional chaining in JavaScript and my findings validated this concept.

Say we have a situation — a pretty common one — where we loop through the items in an array, and sometimes JavaScript throws an error saying that the array itself doesn't exist: cannot map over array (undefined) and so many blah blah blah.

Using this (?.) operator when you loop over that array, helps you check if the array itself exists before returning the UI.

const someItems = [
  'Apple',
  'Mango',
  'Bole',
  'Fufu',
  'Ewedu',
  'Amala',
  'Biscuit',
]
 
const mappedItems = someItems?.map((item) => item)

Sometimes, JavaScript will be JavaScript, and you may not even catch any error, if someItems is undefined.

With TypeScript, all the props you want to pass to a component are supposedly required — by default.

So, modifying <Icon />'s dashboard prop to be optional with optional chaining was appropriate. Here's what the interface looks like now:

interface iconProp {
  name: string
  dashboard?: boolean
}

Scaling this component.

YES, I too was in doubt at first. The question: "If we begin to add more icon folders, how will this component handle them?" came up.

But, then I realized that I've been learning a lot, and concluded that we can chain ternary operations to accept multiple params or conditions.

So, the answer to that question is, add more conditions

import React from 'react'
import { ReactSVG } from 'react-svg'
 
interface iconProp {
  name: string
  dashboard?: boolean
  task?: boolean
  settings?: boolean
  profile?: boolean
}
 
export const Icon: React.FC<iconProp> = ({
  name,
  task,
  settings,
  profile,
  dashboard,
}) => {
  return (
    <ReactSVG
      src={
        dashboard
          ? `/icons/dashboard/${name}.svg`
          : task
          ? `/icons/tasks/${name}.svg`
          : settings
          ? `/icons/settings/${name}.svg`
          : profile
          ? `/icons/profile/${name}.svg`
          : `/icons/${name}.svg`
      }
    />
  )
}

Hence, this component can be used like this, without the optional props.

<Icon name="user" />

And if the need to use an icon from a specific folder arises, You'd pass the respective prop, say settings for example.

<Icon name="gear" settings />

Since all other props in the component's interface are optional, the default path will always be "/icons/${name}.svg"