140

I'm trying to create a stateless React component with optional props and defaultProps in Typescript (for a React Native project). This is trivial with vanilla JS, but I'm stumped as to how to achieve it in TypeScript.

With the following code:

import React, { Component } from 'react';
import { Text } from 'react-native';

interface TestProps {
    title?: string,
    name?: string
}

const defaultProps: TestProps = {
    title: 'Mr',
    name: 'McGee'
}

const Test = (props = defaultProps) => (
    <Text>
        {props.title} {props.name}
    </Text>
);

export default Test;

Calling <Test title="Sir" name="Lancelot" /> renders "Sir Lancelot" as expected, but <Test /> results in nothing, when it should output "Mr McGee".

Any help is greatly appreciated.

Matt Stow
  • 6,053
  • 6
  • 24
  • 26

8 Answers8

155

Here's a similar question with an answer: React with TypeScript - define defaultProps in stateless function

import React, { Component } from 'react';
import { Text } from 'react-native';

interface TestProps {
    title?: string,
    name?: string
}

const defaultProps: TestProps = {
    title: 'Mr',
    name: 'McGee'
}

const Test: React.SFC<TestProps> = (props) => (
    <Text>
        {props.title} {props.name}
    </Text>
);

Test.defaultProps = defaultProps;

export default Test;
Community
  • 1
  • 1
Matt Stow
  • 6,053
  • 6
  • 24
  • 26
  • 4
    This is the correct answer. Recently, SFC was deprecated in favor of FunctionComponent: const Test: React.FunctionComponent = ... – Lenin Apr 18 '19 at 22:26
  • 2
    you can also use React.FC in typescript, rather than React.FunctionComponent – humans Feb 13 '20 at 19:04
  • 7
    What if the property is something like `names?: string[]`? Even if I give a default value like this, it's still optional from typescript's view, so I have to write `props.names?.join(',')` rather than `props.names.join(',')` – Freewind Oct 03 '20 at 17:52
  • 3
    will only work if all props are optional – fullStackChris Oct 05 '21 at 10:30
  • 1
    Hi @Lenin this is not the correct answer as specifying `title` and `name` with `?` lets the user pass `undefined` to those values which should not be case if we have specified the default values to those – AndroidEngineX May 14 '22 at 15:50
107

I've found the easiest method is to use optional arguments. Note that defaultProps will eventually be deprecated on functional components.

Example:

interface TestProps {
    title?: string;
    name?: string;
}

const Test = ({title = 'Mr', name = 'McGee'}: TestProps) => {
    return (
        <p>
            {title} {name}
        </p>
    );
}
kDar
  • 3,246
  • 4
  • 19
  • 23
  • Yes! Part of the beauty of functional components is that you can use all the standard javascript function stuff. – Luke Miles Sep 03 '20 at 14:27
  • 1
    What if the default value is an array `[]` or object `{}`? They can't be used as dependency of hooks since they are created new every time, and will trigger the hook running – Freewind Oct 03 '20 at 17:49
  • 1
    As far as I know, the code should function the same no matter which default value you put in. Would be great if you can link to code that reproduces the problem you are talking about with [] or {} – kDar Oct 03 '20 at 19:27
  • 1
    Offical typescript cheatsheet agrees https://github.com/typescript-cheatsheets/react/blob/main/README.md#you-may-not-need-defaultprops – java-addict301 Jul 09 '21 at 00:01
  • 1
    will only work if all props are optional – fullStackChris Oct 05 '21 at 11:36
  • @fullStackChris You cannot specify default unless it's optional. But you can specify only part of the parameters as optional. So if as parameters you have {title, name = 'McGee'} then calling will output "Mr McGee" – kDar Oct 06 '21 at 12:18
20

Here's how I like to do it:

type TestProps = { foo: Foo } & DefaultProps
type DefaultProps = Partial<typeof defaultProps>
const defaultProps = {
  title: 'Mr',
  name: 'McGee'
}

const Test = (props: Props) => {
  props = {...defaultProps, ...props}
  return (
    <Text>
      {props.title} {props.name}
    </Text>
  )
}

export default Test
Barazu
  • 489
  • 6
  • 10
  • 2
    That looks great, can you explain the first lines? – Erdal G. Nov 10 '20 at 11:22
  • 2
    This is the only correct answer which will work when the props are a mix of required and non-required props. All other solutions concern components which have only optional props. – fullStackChris Oct 05 '21 at 10:29
  • 3
    Is this a typo? Isn't `(props: Props)` supposed to say `(props: TestProps)`? – Ryan Jun 15 '22 at 17:44
5

Update 2022

For function components, there is indeed a possible deprecation of defaultProps field. I don't believe this will happen so soon due to the sheer amount of code already written with it, but a warning being displayed in the console is very likely.

I'm using the solution below, which provides the correct behavior and proper TypeScript validation. It works with mixed defined/undefined properties, and also with properties with/without default values – that is, it covers all cases:

interface Props {
  name: string;
  surname?: string;
  age?: number;
}

const defaultProps = {
  surname: 'Doe',
};

function MyComponent(propsIn: Props) {
  const props = {...defaultProps, ...propsIn};

  return <div>{props.surname}</div>;
}

And VSCode autocomplete is spot on:

VSCode autocomplete

It has been tested with TypeScript 4.7.

rodrigocfd
  • 6,450
  • 6
  • 34
  • 68
  • If you use the component like `` the default value will be overridden. In contrast to the solution proposed by kDar with optional arguments, where specifying an argument as undefined will still use the default one – eto3542 Dec 09 '22 at 10:19
1

Adding my solution to the pot, I think it adds an additional level of readability and elegance onto the existing solutions.

Let's say you have a component MyComponent with a mix of required and optional props. We can separate these required and optional props into two interfaces, combining them for the full prop interface of the component, but only using the optional one to set the default props:

import * as React from "react";

// Required props
interface IMyComponentRequiredProps {
  title: string;
}

// Optional props
interface IMyComponentOptionalProps {
  color: string;
  fontSize: number;
}

// Combine required and optional props to build the full prop interface
interface IMyComponentProps
  extends IMyComponentRequiredProps,
    IMyComponentOptionalProps {}

// Use the optional prop interface to define the default props
const defaultProps: IMyComponentOptionalProps = {
  color: "red",
  fontSize: 40,
};

// Use the full props within the actual component
const MyComponent = (props: IMyComponentProps) => {
  const { title, color, fontSize } = props;
  return <h1 style={{ color, fontSize }}>{title}</h1>;
};

// Be sure to set the default props
MyComponent.defaultProps = defaultProps;

export default MyComponent;
fullStackChris
  • 1,300
  • 1
  • 12
  • 24
  • 1
    Still got error "property is missing type" when called MyComponent in another file and not declare color and fontSize inside it. This error will disapear when all props on IMyComponentOptionalProps are optional e.g color?: red, fontSize?: 40 – wisnuaryadipa Nov 24 '21 at 16:42
  • Here is a codesandbox with this exact component: https://codesandbox.io/s/delicate-pine-e3u2g this pattern works. – fullStackChris Nov 24 '21 at 16:59
  • Ah okay thanks, now i know my problem, default props didn't work well when using styled-component. Type of MyComponent changes that cause error to required all props including declared props. – wisnuaryadipa Nov 25 '21 at 00:41
  • Link updated: https://codesandbox.io/s/react-typescript-optional-props-pattern-e3u2g?file=/src/components/MyComponent.tsx – fullStackChris Jan 08 '22 at 22:15
1

using @rodrigocfd answer i came up with this method,
it's useful in case inheritance is also involved (for instance i needed className being available in my components ,and not encapsulate them in div!)

// base.ts
export interface BaseProps {
  className?: string
}

export const basePropsDefault = {
  className: ''
}

export function setDefaultProps<T, P extends BaseProps>(t: T, defaultProps: P): T {
  // @ts-ignore
  t.defaultProps = {...basePropsDefault, ...defaultProps}
  return t
}


// Card.tsx
import {ReactElement} from "react";
import {setDefaultProps, BaseProps} from "../base";

export interface CardProps extends BaseProps {
  children: ReactElement
  internalPadding?: number,
}

const defaults = {
  internalPadding: 2,
} as CardProps

function Card(props: CardProps) {
  return (
    <>
      <div className={`shadow-lg container p-${props.internalPadding} ${props.className}`}>
        {props.children}
      </div>
    </>
  )
}

export default setDefaultProps(Card, defaults)
nort3x
  • 11
  • 1
  • 2
0

I might be wrong, but passing the a default prop value on the function as the second voted reply says could lead to subtle bugs or over executed useEffects (I don't have enough rep to reply there, so here's a reproducible codesanbox)

Even if it's a really contrived example, and probably in most of the cases just bad component design, I have seen this more than once, even breaking full pages.

Frank Simon
  • 91
  • 1
  • 4
-5

To me, this doesn't look like a typescript issue.

DISCLAIMER: I have only tried this with typescript.

However, the problem is that props always exists (even as an empty object when nothing is passed in). There are 2 workaround for this, though.

The first, unfortunately, kills the super clean curly-brace-less syntax you have, but let's you keep defaultProps around.

interface TestProps {
    title?: string;
    name?: string;
}

const defaultProps: TestProps = {
    title: 'Mr',
    name: 'McGee'
}

const Test = (passedIn: TestProps) => {
    const props = Object.assign({}, defaultProps, passedIn);
    return (
        <p>
            {props.title} {props.name}
        </p>
    );
}

another alternative that might get a little hairy if you have a TON of props, but that lets you keep your original syntax is something like this:

const Test = (props: TestProps) => (
    <Text>
        {props.title || 'Mr'} {props.name || 'McGee'}
    </Text>
);

Hope this helps!

drewwyatt
  • 5,989
  • 15
  • 60
  • 106
  • you don't have to do Object.assign with passedIn props and defaults - that's what React does automatically. – hamczu Dec 12 '17 at 10:30
  • Your solution will probably work, but it's wrong. You should use `defaultProps` static property of a stateless component function to define default property values. – Tomasz Kajtoch Nov 26 '18 at 14:05
  • @TomaszKajtoch According to [this answer](https://stackoverflow.com/a/56443098/1601882) `defaultProps` are being depreciated in functional components can you suggest a way of doing this without using `defaultProps`? – Andy Braham Oct 26 '19 at 06:25