3

So I feel like I'm missing something obvious. I've read the documentation on JSX in non-React contexts and I I don't see what I'm doing wrong. Consider the following code:

/** @jsx pragma */

function pragma(node: any, props: any, ...children: any[]) {
  return node;
}

declare namespace JSX {
  type Element = string;
  interface ElementChildrenAttribute {
    children: {}; // specify children name to use
  }
}

interface PropsType {
  children: string;
  name: string;
}

export const Prop = (props: PropsType) => {};

const foo = <Prop name="foo">hello</Prop>;

The error I get (on the last line) is:

Property 'children' is missing in type '{ name: string; }' but required in type 'PropsType'.

I can "correct" this with:

const foo = <Prop name="foo" children="bye">hello</Prop>;

But I expected that the hello would be assigned as the children. I shouldn't be specifying an explicit children property, right? What am I missing here? I would expect the original to be fine (not generate an error) because the children field should be filled in with the string value "hello" in that case (and not require me to add a children prop?!?).

What am I missing here? Is there some other type/field of the JSX namespace I need to fill in here to make this work?

Clarification: My concern here is that TypeScript isn’t assigning the string “hello” to children. If it did that everything would be fine. As such, it doesn’t seem to be an issue with my prop types so much as I’m missing something in my redeclaration of the JSX namespace that tells the compiler how to handle my “custom” JSX.

Also, it is true I'm not required to have children in my props. But I'm certainly allowed to and I definitely want to because this allows me to narrow the type. This is definitely allowed (see second paragraph here) for precisely this reason. Adding it here does not create a new prop, it simply refines whatever was defined in JSX.ElementChildrenAttribute.

6
  • I am more familiar with using children directly with react, but children should be a default prop type in JSX. Remove the children definition from your propTypes interface and try accessing the text directly via props.children[0]. Commented Dec 20, 2020 at 1:31
  • I assume you're reading this? typescriptlang.org/docs/handbook/… I don't think you need ` children: string;` in the Proptypes interface, since its now "implicit" Commented Dec 20, 2020 at 2:51
  • I do need it because I want more specific type constraints to apply to Prop than just {}. This is explicitly allowed in TypeScript’s JSX implementation. Commented Dec 20, 2020 at 12:42
  • I don't have any errors just using your code if you want children to be a string. How is the project setup? I'm not that familiar with JSX outside of React. Commented Dec 20, 2020 at 15:39
  • I'm running TypeScript 4.1.3. In tsconfig.json, I have target set to "es2019" and "jsx" set to "react". I was pretty much a vanilla TS project except for those changes. If you can't reproduce with those settings, I'll create a repro-repo. Commented Dec 20, 2020 at 16:40

4 Answers 4

1

In React, you can use the type PropsWithChildren<T>. This is what React uses, but you can make your own and replace the React specifics with JSX.

This should handle all the use cases for what Children can be.

type Children = JSX.Element[] | JSX.Element | string | number | boolean | null | undefined;

type PropsWithChildren<P> = P & {
    children?: Children;
}

type PropsType = {
    name: string;
}

export const Bar = ({ name, children }: PropsWithChildren<PropsType>) => (
    <div>
        <p>{name}</p>
        <div>{children}</div>
    </div>
);

I tested and all of these don't throw type errors.

export const ElementArray = () => (
    <Bar name="Foo">
        <div>Hello</div>
        <div>World</div>
    </Bar>
)

export const Element = () => (
    <Bar name="Foo">
        <div>Hello World</div>
    </Bar>
)

export const String = () => (
    <Bar name="Foo">Hello World</Bar>
)

export const Number = () => (
    <Bar name="Foo">{43110}</Bar>
)

export const Boolean = () => (
    <Bar name="Foo">{true}</Bar>
)

export const Null = () => (
    <Bar name="Foo">{null}</Bar>
)

export const Undefined = () => (
    <Bar name="Foo" />
)

Also, you can create your own FC (Functional Component) type using the PropsWithChildren and use it like it's used in React.

type FC<P = {}> = {
    (props: PropsWithChildren<P>): JSX.Element;
}

// usage
export const FooBar: FC<PropsType> = ({ name, children }) => (
    <div>
        <p>{name}</p>
        <p>{children}</p>
    </div>
);
Sign up to request clarification or add additional context in comments.

Comments

1

You have minor omission in your original code of declare global. The following works without any errors:

/** @jsx pragma */

function pragma(node: any, props: any, ...children: any[]) {
  return node;
}

declare global {
  namespace JSX {
    type Element = string;
    interface ElementChildrenAttribute {
      children: {}; // specify children name to use
    }
  }
}

interface PropsType {
  children: string;
  name: string;
}

export const Prop = (props: PropsType) => { return null };

const foo = <Prop name="foo">hello</Prop>; // children assumed

Comments

0

Change

interface PropsType {
  children: string;
  name: string;
}

to

interface PropsType {
  name: string;
}

since children is already in ElementChildrenAttribute. You're essentially overriding children as a prop instead of a "special property"

2 Comments

This “overriding” is specifically allowed and allows you to specialize the type further (which I want to do here). If you look at the very next section after the one you linked to, it says “You can specify the type of children like any other attribute. This will override the default type” and then gives an example. So I don’t see anything wrong with what I’m doing here.
Just to clarify, my concern here is that TypeScript isn’t assigning the string “hello” to children. If it did that everything would be fine. As such, it doesn’t seem to be an issue with my prop types so much as I’m missing something in my redeclaration of the JSX namespace that tells the compiler how to handle my “custom” JSX.
0

OK, I think I figured this out but nothing in the documentation (that I could find) explains why my original code didn't work nor why this approach does.

First, there is a slight error in my code. My Prop component should return something of type JSX.Element and it doesn't. This is an easy fix and doesn't really address the core issue:

export const Prop = (props: PropsType) => "world";
export const Buz = (props: { id: string }) => "test2";

But I also realized something fairly important. I noticed that Visual Studio Code was warning me that the value of JSX in my code above wasn't being used. I assumed this declaration (only) existed for the compiler to see, not for me to use...so I ignored it. But then I tried sticking it in a separate file named jsx.d.ts. This makes a big difference. Here is my jsx.d.ts file:

declare namespace JSX {
  type Element = string;
  interface ElementChildrenAttribute {
    children: {}; // specify children name to use (type seems irrelevant)
  }
}

And here is my code (in a .tsx file):

/** @jsx pragma */

function pragma(node: any, props: any, ...children: any[]) {
  return node;
}

export interface PropsType {
  name: string;
  children: string;
}

export const Prop = (props: PropsType) => "test";
export const Buz = (props: { id: string }) => "test2";

With this change, all these lines now behave as I would expect:

const ok1 = <Prop name="foo">hello</Prop>; // Compiles!
const err1 = <Prop name="bar" />; // Error children missing
const err2 = <Prop name="bar">{5}</Prop>; // Error cannot assign number to string
const ok2 = <Buz id="roger"></Buz>; // Compiles (has no children)
const err3 = (
  <Buz id="roger">
    <Buz id="wilco"></Buz>
    <Buz id="over"></Buz>
  </Buz>
); // Error, has children and shouldn't.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.