skip to Main Content

I expected it works:

interface Point {
  x: number;
  y: number;
}

function A({x, y}: Point = {x: 1, y: 1}) {
  return `x:${x}y:${y}`;
}

export default function App() {
  return <A/>; //error TS2739
}

export default function App() {
  return <>{A()}</>; //ok, no problem
}

Why it doesn’t work?

ps.
Stackoverflow ask me to add some details, but I can’t do that
because code explains problem well.
That is why I add this ps.

2

Answers


  1. The problem likely appears due to how default parameters and destructuring work in React.

    When you call your function A() without arguments, it uses the default parameter.

    However when you use <A />, React passes an empty props object "{}" to A.
    Function A then receives this empty object and tries to destructure x and y from it.
    But because x and y are missing from the props object, their value will be undefined.

    To solve your issue either you pass a value to your component

    <A x={1} y={1} />
    

    Or set x and y as optional in the interface

    interface Point {
      x?: number;
      y?: number;
    }
    

    and then handle the undefined case for each property inside your "A" component function.

    Lastly you need to return a valid jsx element and not a string.

    Like this:

    function A({x, y}: Point) {
      return <>{`x:${x}y:${y}`}</>;
    }
    

    Of course it is not required to be a React fragment

    Login or Signup to reply.
  2. When you write <A />, it ends up calling A({}) with an empty object as argument; it does not call A() with no argument. So you need to write A such that it accepts an empty object and does what you want with it. In the comments you implied that you wanted to either require both x and y, or you want to prohibit both x and y. You don’t want to make x and y independently optional.

    If so then you need something like

    type ProhibitKeysOf<T> = { [P in keyof T]?: never }
    type EmptyObj = ProhibitKeysOf<Point>;
    /* type EmptyObj = {
        x?: never;
        y?: never;
    } */
    
    function A({ x = 1, y = 1 }: Point | EmptyObj) {
        const r = `x:${x}, y:${y}`
        console.log(r);
        return r;
    }
    

    Note that TypeScript doesn’t have a type that means "empty object" the way we’re talking about it. If you write {} like Point | {} then it will accept anything at all, since {} means "a type with no known properties" which isn’t the same as "a type known not to have properties". So instead we can use {x?: never, y?: never} to mean a type where x and y are either missing or undefined. My little ProhibitKeysOf<T> utility type is just there to show how to extend this to more properties if needed.

    Note that there’s no point in giving A a default argument like {x: 1, y: 1}, because A will never be called without an argument. Instead, you want something like default destructured properties, like {x = 1, y = 1} above, so that when you pass in {}, x and y get the default properties of 1. This would technically mean if you pass in {x: 2} you’d get x is 2 and y is 1, which you don’t want… but that possibility is precluded by the annotation Point | EmptyObj.

    And now you can call A like:

    <A x={1} y={2} />; // okay
    <A x={1} />; // error
    <A /> // okay
    

    where you can pass in either both x and y or neither x nor y, and if you try passing just one then you get an error.

    Playground link to code

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search