Rico Kahler
Rico Kahler

Reputation: 19242

React-Typescript: How do I type a default prop?

Let's say I have this component class:

interface SomeComponentProps {
  someOptionalProperty?: string;
}
interface SomeComponentState { /* ... */ }

class SomeComponent extends React.Component<SomeComponentProps, SomeComponentState> {
  static defaultProps = {
    someOptionalProp = 'some default value',
  }

  method() {
    const shouldNotBeOptional = this.props.someOptionalProp;
  }
}

How can I assert that this.props.someOptionalProp is not undefined within the class but is also optional when trying to using the component?

What I've been doing is just ignoring defaultProps altogether by creating getters that

return `this.props.someOptionalProp || 'the default value'`;

but I'd like to use defaultProps because my team uses it.

Upvotes: 2

Views: 1994

Answers (4)

IdanDavidi
IdanDavidi

Reputation: 482

This issue has been discussed and resolved in here. Now you simply can do that:

interface Props {
  thing: string; // note: not optional within class, but is optional when writing due to the default
}

class Thing extends React.Component<Props> {
   static defaultProps = {
      thing: 'hello'
   }

  render() {
    console.log(this.props.thing) // strict type from `Props`
    // ...
 }
}
// ..
<Thing /> // works, thanks to `defaultProps`

It works for me in Typescript 3.1.6 and React 16.5.0

Upvotes: 4

Codepunkt
Codepunkt

Reputation: 542

I use a higher order component with Typescript 2.8 in my codebase:

const withDefaultProps = <P extends object, DP extends Partial<P>>(
  defaultProps: DP,
  component: React.ComponentType<P>
) => {
  type ActualProps = Partial<DP> & Pick<P, Exclude<keyof P, keyof DP>>
  component.defaultProps = defaultProps
  return component as React.ComponentType<ActualProps>
}

interface IComponentProps {
  someOptionalProp: string
}

class SomeComponent extends React.Component<IComponentProps, {}> {
  method() {
    // this is always defined
    const shouldNotBeOptional = this.props.someOptionalProp;
  }
}

// when using this like <SomeComponent />, someOptionalProp is optional
export default withDefaultProps(
  { someOptionalProp: 'defaultValue' },
  SomeComponent
)

Upvotes: 1

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 249676

You could use a higher order component (HOC) that will just change the type of the props to be optional where they are present in defaultProps

interface SomeComponentProps {
    required: string;
    someOptionalProperty: string;
    trulyOptional?: string;
}
interface SomeComponentState { /* ... */ }

class SomeComponent extends React.Component<SomeComponentProps, SomeComponentState> {
    static defaultProps = {
        someOptionalProperty : 'some default value',
    }

    method() {
        const shouldNotBeOptional = this.props.someOptionalProperty;
    }
}

type SameTypeOrCustomError<T1, T2> = T1 extends T2 ? T2 extends T1 ? T1 : "Error Different Types": "Error Different Types";
type FilterOptional<T> = Exclude<{ [P in keyof T]: undefined extends T[P]? P: never }[keyof T], undefined>;

type d = FilterOptional<SomeComponentProps>
type PropsWithDefault<TProps extends TDefaults, TDefaults> = 
    { [P in Exclude<keyof TProps, keyof TDefaults | FilterOptional<TProps>>]: TProps[P] } &
    { [P in keyof TDefaults | FilterOptional<TProps>]?: TProps[P] }; 

type ReactProps<T> = T extends React.Component<infer Props, any> ? Props: never;
type ReactState<T> = T extends React.Component<any, infer TState> ? TState: never;
type ChangeReactProps<TComponent extends React.Component<any, any>, TNewProps> = {
    new (props: TNewProps, context?: any) : React.Component<TNewProps, ReactState<TComponent>>
};

function withDefaults<T extends { new (...args: any[]) : any, defaultProps: Partial<ReactProps<InstanceType<T>>> }>(ctor: T) 
    : ChangeReactProps<InstanceType<T>, PropsWithDefault<ReactProps<InstanceType<T>>, T['defaultProps']>>  {
    return ctor; // we just chage type, we can return the same class 
}


const SomeComponent2 = withDefaults(SomeComponent);

let d = <SomeComponent2  required=""  /> //ok 
let x = <SomeComponent2  required="" trulyOptional="" /> //ok 

Upvotes: 1

ed&#39;
ed&#39;

Reputation: 1895

Not really possible as it stands with TS 2.8.3, there are some issues open about it already

On react types: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/11640

On the TS repo: https://github.com/Microsoft/TypeScript/issues/23812

Best you can achieve currently I believe is by using the ! operator to tell the compiler that you know something can't be null

const shouldNotBeOptional = this.props.someOptionalProp!;

Upvotes: 1

Related Questions