react: FunctionComponent and ComponentClass are not compatible with LibraryManagedAttributes

Annotating functions and classes with FunctionComponent and ComponentClass breaks LibraryManagedAttributes due to the static defaultProps property on those interfaces being set to optional.

import { FunctionComponent, ComponentClass, Component } from 'react'

export interface Props {
  foo: string
  bar?: boolean
}

const TestFunction: FunctionComponent<Props> = props => <div />
TestFunction.defaultProps = {
  foo: '',
}

const TestClass: ComponentClass<Props> = class TestClass extends Component<Props> {
  static defaultProps = {
    foo: '',
  }

  render() {
    return <div />
  }
}

// type is never
type TestDefaultProps = typeof TestFunction extends { defaultProps: infer D } ? D : never
type TestClassDefaultProps = typeof TestClass extends { defaultProps: infer D } ? D : never

// type is Props because typeof Test does not extend { defaultProps } but rather { defaultProps? }
type TestManagedProps = JSX.LibraryManagedAttributes<typeof TestFunction, Props>
type TestClassManagedProps = JSX.LibraryManagedAttributes<typeof TestClass, Props>

This causes defaultProps to be completely ignored by JSX.LibraryManagedAttributes. We should probably remove it as a recommendation from the cheat sheet for now.

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Comments: 19 (3 by maintainers)

Commits related to this issue

Most upvoted comments

I was thinking more of something along these lines:

const defaultProps = {
  foo: 'bar',
};

type Props = typeof defaultProps & {
  optional?: string
};

export const TestFunction: FC<Props> = props => {
  const { foo } = {
    ...defaultProps,
    ...props,
  };
  return (
    <div>{foo}</div>
  );
};

As that’s the pattern that is currently advised in the doc.

as a side note - i dont find myself ever using defaultProps for function components as i can assign when i destructure…

@sw-yx Well looks like you’re not the only one: Deprecate defaultProps on function components

Default values inside parameters (and when destructured) are better understood by the compiler. It will allow you to not have to hack together the props interface. The compiler knows it will always be defined inside the implementation but optional when consumed.

It’s not clear to me if we should just blanket advise against using React.FC altogether

I wouldn’t do that either. I think it’s perfectly fine to explain the drawbacks of

explicit annotations on function expressions:

  • -no defaultProps
  • +explicit type annotation (helps with some gotchas concerning return types of function components)
  • -no additional statics
  • -no generic props

function declarations:

  • +defaultProps in ts >= 3.1
  • no implicit children
  • -needs explicit return type to catch wrong return type
  • +additional statics
  • +generic props

Personally I don’t like implicit children anyway. Not all of my components handle children. I would hope typescript would recognize some day that <Foo>bar</Foo> is equivalent to <Foo children="bar" /> and report an error on both call sites if the props of Foo don’t define a children property.

thanks - i’ll update accordingly. why is it considered a bad idea to have implicitly typed children? i thought that was a major benefit of using React.FC.

as a side note - i dont find myself ever using defaultProps for function components as i can assign when i destructure…

The linter error should be ignored or turned off, because destructuring and default values already achieves the same as .defaultProps. The linter errors are just rules of thumb that are contextual, and this specific rule doesn’t apply in this case.

If I could rewrite react types implicit children would be removed. It causes a lot more problems and was only placed there for convenience.

I always explicitly include children in my props interfaces anyway when I do support them.

i mean… destructure and assign defaults?

const TestFunction: FunctionComponent<Props> = { foo = "bar" } => <div>{foo}</div>