Component with function child yields error "Its return type 'ReactNode' is not a valid JSX element." - reactjs

Consider the following react code: (codesandbox)
import type { ReactNode } from 'react';
type RenderCallbackProps = {
children: (o: { a: number }) => ReactNode,
};
function RenderCallback({ children }: RenderCallbackProps) {
return (children({ a: 123 }));
}
export default function App() {
return (
<RenderCallback>
{({ a }) => (
<h1>{a}</h1>
)}
</RenderCallback>
);
}
This causes the error message:
function RenderCallback({ children }: RenderCallbackProps): ReactNode
'RenderCallback' cannot be used as a JSX component.
Its return type 'ReactNode' is not a valid JSX element.
Type 'undefined' is not assignable to type 'Element | null'.ts(2786)
Why?
Where is the undefined coming from?
How to fix that?

Where is the undefined coming from?
That's part of what ReactNode means. ReactNode is: ReactChild | ReactFragment | ReactPortal | boolean | null | undefined, or basically all of the stuff that you could legally put as the child of a <div>. But while you can put a undefined as a child of a div, it can't be the only thing returned by a component, so you get an error when you try to do return (children({ a: 123 }));
You can fix this one of two ways. If you want to continue to allow null/undefined/boolean etc to be passed in, then just make sure that RenderCallback always wraps it in an element. A fragment will be enough:
function RenderCallback({ children }: RenderCallbackProps) {
return (
<>
{children({ a: 123 })}
</>
)
}
Or if you don't want to allow those values, then you can change the types on the callback. For example only allowing elements:
children: (o: { a: number }) => ReactElement,

Related

Index children of React component with TypeScript

I'm making a component that has multiple sets of children.
The question React component with two sets of children suggests to index children, as in props.children[0]. This works great in JavaScript.
But in TypeScript, I'm getting a type error, even though the code works fine at runtime.
function MyComponent(props: { children: React.ReactNode }) {
return <>
...
{props.children[0]}
...
{props.children[1]}
...
</>;
}
TypeScript fails with the following error messages on props.children[0]:
Object is possibly 'null' or 'undefined'.
ts(2533)
Element implicitly has an 'any' type because expression of type '0' can't be used to index type 'string | number | boolean | ReactElement<any, string | JSXElementConstructor<any>> | ReactFragment | ReactPortal'.
Property '0' does not exist on type 'string | number | boolean | ReactElement<any, string | JSXElementConstructor<any>> | ReactFragment | ReactPortal'.
ts(7053)
How do I make it typecheck?
You need to tell Typescript that your component needs multiple children by putting the array brackets:
function MyComponent(props: { children: React.ReactNode[] }) {
// ...
}
If your component accepts exactly 2 children, a tuple type would be preferable so that Typescript will enforce having 2 children:
function MyComponent(props: { children: [React.ReactNode, React.ReactNode] }) {
// ...
}
If your component accepts 2 or more children, you can specify a tuple with a rest element:
function MyComponent(props: { children: [React.ReactNode, ...React.ReactNode[]] }) {
// ...
}
You can use ReactNode[] instead of ReactNode
function MyComponent(props: { children: React.ReactNode[] }) {
return <>
{props.children[0]}
{props.children[1]}
</>;
}
function TestComponent() {
return <MyComponent>
<span>Element 1</span>
<span>Element 2</span>
{'string test'}
{2}
{true}
</MyComponent>
}
Playground
If your component always attempts to render at least 2 child nodes, then you should explicitly type the children parameter that way:
TS Playground
import {type ReactElement, type ReactNode} from 'react';
function MyComponent(props: {
children: [ReactNode, ReactNode, ...readonly ReactNode[]];
}): ReactElement {
return <>
...
{props.children[0]}
...
{props.children[1]}
...
</>;
}
By doing so, TypeScript will emit a compiler error diagnostic if you don't provide at least two children:
function App (): ReactElement {
return (
<MyComponent>{/*
~~~~~~~~~~~
Type '{ children: []; }' is not assignable to type '{ children: [ReactNode, ReactNode, ...ReactNode[]]; }'.
Types of property 'children' are incompatible.
Type '[]' is not assignable to type '[ReactNode, ReactNode, ...ReactNode[]]'.
Source has 0 element(s) but target requires 2.(2322) */}
</MyComponent>
);
}
Similarly, when you only provide one child node:
TS Playground
function App (): ReactElement {
return (
<MyComponent>{/*
~~~~~~~~~~~
This JSX tag's 'children' prop expects type '[ReactNode, ReactNode, ...ReactNode[]]'
which requires multiple children, but only a single child was provided.(2745) */}
<div></div>
</MyComponent>
);
}
But as soon as you provide at least two, the diagnostic disappears:
TS Playground
function App (): ReactElement {
return (
<MyComponent>
<div></div>
<div></div>
</MyComponent>
);
}

How to write a Typescript React Component Wrapper

A custom ReactJS wrapper component should be used to wrap code and only render it, if the user has the required permission. Therefore, it needs to accept two parameters, the children that shall be rendered as well as the permission it needs to check for.
The idea is to use it like this:
const AdminTools = () => {
return (
<RequirePermission require_permission="ui:see_admin_menu">
<>
<h1>Sudo mode</h1>
<Link href="/intern/users/">
<a>
<ActionCard link>User management</ActionCard>
</a>
</Link>
</>
</RequirePermission>
);
};
What I came up with so far is the following code:
const RequirePermission = (children: React.FC<{}>, required_permission: string) => {
const user = useContext(AuthContext);
let hasPermission = false;
if (user.state == AuthState.Authorized) {
user.user?.realm_access?.roles.forEach(role => {
if (permissions[role].includes(required_permission)) {
hasPermission = true;
}
});
}
if (hasPermission) {
return children;
} else {
return <div />;
}
};
export default RequirePermission;
When using the code snippet as described above, the following error is thrown:
Type '{ children: Element; require_permission: string; }' is not assignable to type 'IntrinsicAttributes & FC<{}>'.
Property 'children' does not exist on type 'IntrinsicAttributes & FC<{}>'.ts(2322)
'RequirePermission' cannot be used as a JSX component.
Its return type 'FC<{}> | Element' is not a valid JSX element.
Type 'FunctionComponent<{}>' is missing the following properties from type 'Element': type, props, keyts(2786)
I don't really understand the error message to be frank. Any help would be much appreciated.
//Edit:
Error messages given by proposed code:
This JSX tag's 'children' prop expects a single child of type 'ReactChildren', but multiple children were provided.ts(2746)
and
'RequirePermission' cannot be used as a JSX component.
Its return type 'Element | ReactChildren' is not a valid JSX element.
Type 'ReactChildren' is missing the following properties from type 'Element': type, props, key
Try this, importing ReactChildren from react.
I wish i could explain this better, but i know typescript is expecting a JSX/TSX element, so we could use ReactElement for the return type.
For a better explanation on the children type
https://stackoverflow.com/a/58123882/7174241
import React, { ReactChildren, ReactElement, ReactNode } from "react";
interface RequireType {
children: ReactChildren | ReactNode | ReactElement;
required_permission: string;
}
const RequirePermission = ({ children, required_permission }: RequireType):ReactElement => {
const user = useContext(AuthContext);
let hasPermission = false;
if (user.state == AuthState.Authorized) {
user.user?.realm_access?.roles.forEach((role) => {
if (permissions[role].includes(required_permission)) {
hasPermission = true;
}
});
}
return <>{hasPermission ? children : null}</>
};
export default RequirePermission;

Type 'Element[]' is missing the following properties when handling react children

I have a parent component that provides its children to a child component. The children can either be a node or an array of nodes, and depending on if it is a node or an array of nodes the children in the ChildComponent are rendered differently. However, when I a render the ChildComponent in the ParentComponent, I get the following Typescript error:
TS2786: 'ChildComponent' cannot be used as a JSX component.   Its return type 'Element | Element[]' is not a valid JSX element.     Type 'Element[]' is missing the following properties from type 'Element': type, props, key
Please see the below for the code. How can I render the ChildComponent without any Typescript errors? Thanks!
import React from 'react';
import styles from './component.scss';
interface Props {
children: React.ReactChildren;
}
const ChildComponent = ({ children }: Props): JSX.Element | JSX.Element[] => {
if (React.Children.count(children)) {
return React.Children.map(children, (child) => (
<div className={styles.childContainer}>{child}</div>
));
}
return <div className={styles.singleContainer}>{children}</div>;
};
const ParentComponent: React.FC<Props> = ({ children }) => (
<div className={styles.container}>
<ChildComponent>{children}</ChildComponent>
</div>
);
export default ParentComponent;
In your current code, the return type is JSX.Element[] from if block (incorrect) but JSX.Element (correct) from else part. That's the reason of the error you are seeing.
To fix it, you need to return single JSX expression from ChildComponent as "JSX expressions must have one parent element".
You can use a parent element like div, span or simply use React.Fragment (shorthand <></>):
const ChildComponent = ({ children }: Props) => {
if (React.Children.count(children)) {
return (
<> // HERE
{React.Children.map(children, (child) => (
<div className={styles.childContainer}>{child}</div>
))}
</>
);
}
return <div className={styles.singleContainer}>{children}</div>;
};
And children type should be ReactNode instead of ReactChildren:
interface Props {
children: React.ReactNode;
}
As these are the definitions of both the typings:
type ReactNode = ReactChild | ReactFragment | ReactPortal | boolean | null | undefined;
interface ReactChildren {
map<T, C>(children: C | C[], fn: (child: C, index: number) => T):
C extends null | undefined ? C : Array<Exclude<T, boolean | null | undefined>>;
forEach<C>(children: C | C[], fn: (child: C, index: number) => void): void;
count(children: any): number;
only<C>(children: C): C extends any[] ? never : C;
toArray(children: ReactNode | ReactNode[]): Array<Exclude<ReactNode, boolean | null | undefined>>;
}
Also, I would suggest not to use React.FC.
Here is a demo.
Edit:
PS: The else block <div className={styles.singleContainer}>{children}</div> and the if condition with count is not really required.

What type is best to pass one React Component to another

If I want to pass some array of React Components to a React Component, I thought I could use the type Component: React.ComponentClass<any> | React.StatelessComponent<any> in props (as per this issue), for example:
interface Props {
elements: (React.ComponentClass<any> | React.StatelessComponent<any>)[];
}
const App = (props: Props) => (
<>
{props.elements.map(X => <X />)}
</>
)
But when following this pattern, I get the following error:
Element type 'X' does not have any construct or call signatures
Ok, sure; it needs to be a function, so maybe:
interface Props {
elements: (() => (React.ComponentClass<any> | React.StatelessComponent<any>))[];
}
But then I am told that this type is missing properties type, key, and props.
My question is, what type should I use in this situation?
Going with the type
(() => React.ReactElement)[]
Seems to remove any errors, but is this the best way to do this?
Actually an even better type is React.ReactNode, that is:
type ReactNode = ReactChild | ReactFragment | ReactPortal | boolean | null | undefined;
(React.ReactElement is part of ReactChild).
Basically React.ReactNode is anything that can be rendered inside jsx:
const TestApp: React.FunctionComponent<{ elements: Array<React.ReactNode> }> = props => {
return <>{...props.elements}</>;
};
You should also look at reacts child props. That can be used to compose elements like writing plain HTML:
<Test>
<Component1 />
<Component1 />
</Test>
And your test component would be:
const TestApp: React.FunctionComponent = props => {
return <>{...props.children}</>;
};

What is proper type for React's children as function?

I'm passing a children as a function, returning React.ReactNode into Provider HOC like follows:
<Provider variables={{ id: "qwerty" }}>
{data => (
<ExampleComponent data={data} />
)}
</Provider>
Here's a provider component example code:
type Props = {
id: string;
children: any; // What type should be put here?
};
const Provider: React.SFC<Props> = ({ id, children }) => {
const { data, loading, error } = useQuery<req, reqVariables>(query, {
variables: { id }
});
if (loading) return "Loading...";
if (error) return "Error";
if (!data) return "No data";
return children(data);
}
If I'm not specifying any type for children, so leaving it to default to React.ReactNode, then line return children(data); shows an error:
Cannot invoke an expression whose type lacks a call signature. Type 'string | number | boolean | {} | ReactElement ReactElement Component)> | null) | (new (props: any) => Component)> | ReactNodeArray | ReactPortal' has no compatible call signatures.
If I'm specifying children explicitly in type Props as any, everything works great, but it's Wrong from a Typescript and types perspective.
What is a proper type to be put for props.children, when a function returning React.ReactNode is passed instead of React.ReactNode itself, so Typescript would accept it as a valid "Render function" and will allow calling children like function?
You can specify the children type to be a function which returns a ReactNode
type Props = {
id: string,
children: (props: InjectedCounterProps) => React.ReactNode
};
JSX.Element can also be the type, in case the children being 'DOM element'.

Resources