I am trying to use the JSX syntax to allow users to create behavior trees with a nice hierarchical syntax:
const EnsureTargetNearby = (
<Fallback comment="Ensure mob nearby">
<IsMonsterNearby mobTypes={mobTarget} />
<MoveTo destinationOrKey={mobTarget} />
</Fallback>
);
const EnsureAlive = (
<Fallback comment="Ensure char alive">
<IsAlive />
<ThrottleDecorator delay={1000}>
<Respawn />
</ThrottleDecorator>
</Fallback>
);
However, I'm getting type issues related to the children
prop. This error occurs on each node that expects one or more children:
Property 'children' is missing in type '{ comment: string; }' but required in type 'FallbackProps'.
I have prepared a minimal example showcasing the error: Link to playground
The full code in case the playground becomes obsolete at some point:
/*
* @jsx jsx
*/
// Base node for the Behavior Tree
export abstract class BTNode {
abstract tick(): boolean;
}
export type JSXNode = BTNode;
export interface JSXChildren {
children?: JSXNode | JSXNode[];
}
export type FunctionComponent = (props: Record<string, unknown>) => BTNode;
export type ClassComponent = new (props: Record<string, unknown>) => BTNode;
declare namespace JSX {
export interface IntrinsicElements { }
// Declare the shape of JSX rendering result
// This is required so the return types of components can be inferred
export type Element = BTNode;
// Declare the shape of JSX components
export type ElementClass = BTNode;
}
// eslint-disable-next-line @typescript-eslint/ban-types
const isESClass = (fn: Function): fn is new (...args: any[]) => any =>
typeof fn === "function" &&
Object.getOwnPropertyDescriptor(fn, "prototype")?.writable === false;
export function jsx(
tag: FunctionComponent | ClassComponent,
props: Record<string, unknown>,
...children: BTNode[]
): JSX.Element {
const fullProps = { ...props, children };
if (tag instanceof BTNode) {
return tag;
}
if (isESClass(tag)) {
const ClassTag = tag as new (props_: Record<string, unknown>) => JSX.Element;
return new ClassTag(fullProps);
}
return tag(fullProps);
}
export interface FallbackProps {
comment: string;
children: BTNode[];
}
export class Fallback extends BTNode {
protected children: BTNode[] = [];
comment: string;
constructor({ comment, children }: FallbackProps) {
super();
this.children = children;
this.comment = comment;
}
override tick(): boolean {
for (const child of this.children) {
const status = child.tick();
if (status !== false) {
return status;
}
}
return false;
}
}
export class AlwaysTrue extends BTNode {
override tick(): boolean {
return true;
}
}
// The error is on the opening Fallback tag below
const behaviorTree = (
<Fallback comment="My description">
<AlwaysTrue />
<AlwaysTrue />
</Fallback>
)
With my current configuration it seems like the JSX
namespace is entirely ignored. If I changed to stupid types like null
or string
, nothing changes in the type errors.
Despite the type errors, this code executes perfectly well once forcibly transpiled. It's purely the type that is not working.
I initially followed the tutorial on https://dev.to/afl_ext/how-to-render-jsx-to-whatever-you-want-with-a-custom-jsx-renderer-cjk but I never managed to make jsxImportSource
work.
I tried reading https://www.typescriptlang.org/docs/handbook/jsx.html but the docs are a bit too high level and don't go into much details regarding the specifics. Especially regarding the JSX
namespace which I don't understand how to make properly available.
Of course I tried looking for people with similar problems but nothing helped:
Thanks to the accepted answer, I've fixed the playground which now works as expected: Link to playground
This was discussed on TypeScript discord, and it transpired that modifying the global JSX namespace required it to be inside a declare global
block, because it is a global interface not a module-scoped interface, and also that JSX.ElementChildrenAttribute needed to be set to tell TypeScript how the JSX compiler handles children.
declare global {
namespace JSX {
export interface IntrinsicElements { }
// Declare the shape of JSX rendering result
// This is required so the return types of components can be inferred
export type Element = BTNode;
// Declare the shape of JSX components
export type ElementClass = BTNode;
// Tell TS what happens to children
interface ElementChildrenAttribute {
children: {}; // specify children name to use
}
}
}