Search code examples
reactjstypescript

How to define component default props once?


The recommended way to set default props for a component on the official documentation is the following:

interface Props {
  /**
   * default 100
   */
   size?: number
}


function Avatar({ size = 100 }: Props) {
      // ...
}

The problem: the default value is defined twice (once in the interface comment and once in the function signature). Is there a way to define it in one place only?


Solution

  • The problem: the default value is defined twice (once in the interface comment and once in the function signature). Is there a way to define it in one place only?

    Actually, you want to define "it" in many places.That may sound counter-intuitive (isn't DRY a thing?) so let's dig in.

    What are we defining?

    I added some scare-quotes above so hopefully that makes it clear that the word "it" is the source of the confusion. But lets start with what an interface is. An interface, or so the admittedly poor documentation would have us believe

    is a data type that acts as an abstraction of a class. It describes a set of method signatures, the implementations of which may be provided by multiple classes that are otherwise not necessarily related to each other.

    I actually think that page has a lot of mistakes, in particular this isn't really about classes as your code clearly demonstrates, but the key word for our purposes here is "multiple".

    Interfaces are expected to have multiple concrete implementations. These can be different as long as they conform to the signature. The problem that you have is that your interface isn't an interface at all: the docstring comment is the first clue because for an interface you wouldn't necessarily expect to have the same default value for every implementation! If have an interface Sized that looks like this:

    interface Sized {
      size: 'small' | 'medium' | 'large' | 'brobdinagian'
    }
    

    Then class Ant and class Horse which both implement that interface are going to have very different default values. And I picked horse deliberately: consider the difference between a minature horse and a Belgian draft horse. A specific instance of Horse might have a different value that overrides your default horse size regardless of what you pick.

    So it doesn't make sense to make the default part of the interface: they are two different things. The interface defines the schema (optional number in this case) but the default value of 100 is defining the default value for a particular concrete implementation of that interface.

    So I would delete the comment entirely. You can add a TSDoc comment to the function that should give e.g. appropriate intellisense, something like:

    /**
     * React component representing the user's Avatar image.
     *
     * @param [props] The component props.
     * @param props.size Optional image size, defaults to 100.
     */
    function Avatar({ size = 100 }: Props): JSX.Element {
          // ...
    }
    

    Note that unlike the traditional JSDoc usage you can omit the type information since it's already present in the function signature (I added the return type annotation). And now if you ever need or want to re-use your Props interface it isn't so tightly coupled to the needs of the Avatar component.