20

I have a React project that I'm converting from JS to TS. An issue I'm running into is that TSX React is assuming that all properties defined in a functional component are required props.

// ComponentA.tsx
class ComponentA extends React.Component<any, any> {
  render() {
    /* Type '{ equalWidth: true; children: Element[]; }' is not assignable to type '{ children: any; className: any; equalWidth: any; }'.
     * Property 'className' is missing in type '{ equalWidth: true; children: Element[]; }'.' */
    return <ComponentB equalWidth />
  }
}

and

// ComponentB.js
const ComponentB = ({ children, className, equalWidth }) => {
  return (...)
}

is there a way to signal to TS that JSX component props are all optional?

Tyler Sebastian
  • 9,067
  • 6
  • 39
  • 62
  • Related - [Optional property on a component](https://stackoverflow.com/q/65981948/104380) – vsync Dec 07 '21 at 14:02

2 Answers2

12

One simplest option will be setting a default value for your optional props. As an example, if className is optional you can change your ComponentB.js to something like this.

const ComponentB = ({ children, className="", equalWidth }) => {
  return (...)
}

Also if you deconstruct your props in the function body instead of the signature TS will not complain about typings.

const ComponentB = (props) => {
  const { children, className, equalWidth } = props;
  return (...)
}
Tharaka Wijebandara
  • 7,955
  • 1
  • 28
  • 49
  • 1
    Good solution! The issue is that TS assumes that if you have `onClick = null`, `onClick`'s type is the literal `null` - instead of `null` as a default – Tyler Sebastian Aug 25 '17 at 17:33
  • I'm going to give the bounty to you. Although the solution isn't _exactly_ what I was looking for - there are some issues (as I've described in my comment above) - it's fairly easy and straight forward to work around. Thanks! – Tyler Sebastian Aug 28 '17 at 19:49
  • 1
    @TylerSebastian I'm glad if it helped you somehow – Tharaka Wijebandara Aug 28 '17 at 22:55
8

Assuming that ComponentB.js is going to end up as a TypeScript component:

interface ComponentBProps {
    children?: ReactNode;
    className?: string;
    equalWidth?: boolean;
}

const ComponentB = ({ children, className, equalWidth }: ComponentBProps) => {
    // 
};

In the special case where all properties are optional, you could remove the ? from each property on the interface and use Partial<ComponentBProps>, but I guess that at least something will end up being a required prop.


If you want to keep ComponentB.js as it is, then an alternative solution is to create a type definitions file:

import { ReactNode, StatelessComponent } from "react";

interface ComponentBProps {
    children?: ReactNode
    className?: string;
    equalWidth?: boolean;
}

export const ComponentB: StatelessComponent<ComponentBProps>;

If you put in this the same directory as the JavaScript file and name is ComponentB.d.ts, then you should be able to import ComponentB in your TypeScript file.

The way I have written the definition assumes that the component is a named export, not the default, i.e. it is exported like export const ComponentB in the .js file.

(probably) working example: https://github.com/fenech/tsx-jsx

Tom Fenech
  • 72,334
  • 12
  • 107
  • 141
  • 1
    Well, yes and no: yes, `ComponentB` is eventually going to be converted to TypeScript, but not at the moment. I can't annotate a `.js` file, so I can't add in TS types - and if that was a possibility, I would be able to just annotate it properly, then. – Tyler Sebastian Jul 31 '17 at 16:44
  • @Tyler I made a little example which uses a `.d.ts` file and added a link – Tom Fenech Aug 30 '17 at 10:13