This TypeScript error occurs when a component's children prop is typed too strictly, preventing strings from being passed as children. It typically happens when using JSX.Element instead of React.ReactNode.
This error appears in TypeScript projects when you've restricted the type of the children prop in a way that excludes strings. React normally accepts strings as valid children, but if you type children as JSX.Element or JSX.Element[], TypeScript will reject string children at compile time. The error message suggests using an array because TypeScript thinks you're trying to pass multiple children but only provided a single string. However, the real issue is that the children type doesn't include string as a valid type. This is a TypeScript type checking error, not a runtime React error. React itself has no problem rendering strings as children - this is entirely about how you've defined your component's TypeScript types.
The most common fix is to use React.ReactNode for the children prop, which accepts strings, numbers, elements, and more:
// ❌ Too strict - rejects strings
interface ButtonProps {
children: JSX.Element;
}
// ✅ Correct - accepts strings and elements
interface ButtonProps {
children: React.ReactNode;
}
export function Button({ children }: ButtonProps) {
return <button>{children}</button>;
}
// Now this works:
<Button>Click me</Button>React.ReactNode is the standard type for children and includes:
- string
- number
- React.ReactElement
- React.ReactFragment
- null
- undefined
- boolean
React provides a utility type PropsWithChildren that automatically adds a ReactNode children prop:
import { PropsWithChildren } from 'react';
// Without other props
type ButtonProps = PropsWithChildren;
// With additional props
interface ButtonBaseProps {
variant: 'primary' | 'secondary';
onClick?: () => void;
}
type ButtonProps = PropsWithChildren<ButtonBaseProps>;
export function Button({ children, variant, onClick }: ButtonProps) {
return (
<button className={variant} onClick={onClick}>
{children}
</button>
);
}This approach is cleaner and ensures consistency across your codebase.
Only restrict children types if you genuinely need to enforce a specific structure:
// If you ONLY accept specific components:
interface TabsProps {
children: React.ReactElement<TabProps> | React.ReactElement<TabProps>[];
}
// But note: this prevents string children
<Tabs>
<Tab label="First">Content</Tab> {/* ✅ Works */}
Some text {/* ❌ Error */}
</Tabs>
// If you need validation beyond types, use runtime checks:
export function Tabs({ children }: PropsWithChildren) {
React.Children.forEach(children, (child) => {
if (!React.isValidElement(child) || child.type !== Tab) {
throw new Error('Tabs only accepts Tab components as children');
}
});
return <div className="tabs">{children}</div>;
}Runtime validation is more flexible than TypeScript types for complex children requirements.
If you're working with a third-party library where you cannot change the type definitions:
// Library component only accepts JSX.Element
import { StrictComponent } from 'some-library';
// ❌ Doesn't work
<StrictComponent>
Hello World
</StrictComponent>
// ✅ Wrap in fragment
<StrictComponent>
<>Hello World</>
</StrictComponent>
// ✅ Or use a span/div
<StrictComponent>
<span>Hello World</span>
</StrictComponent>This wraps your string in a JSX element, satisfying the type requirement.
Why React.ReactNode is the standard:
React.ReactNode is defined as:
type ReactNode =
| ReactElement
| string
| number
| ReactFragment
| ReactPortal
| boolean
| null
| undefined;This covers everything React can render, making it the most flexible and appropriate type for children in most cases.
JSX.Element vs React.ReactElement:
JSX.Element is actually an alias for React.ReactElement with any types:
declare global {
namespace JSX {
interface Element extends React.ReactElement<any, any> {}
}
}Both exclude string, number, null, and undefined, which are valid React children.
Type safety vs flexibility trade-off:
While TypeScript can't prevent you from passing wrong component types at compile time (everything is seen as JSX.Element), runtime validation with React.Children or React.isValidElement is more effective for enforcing component structure requirements.
Children as a render prop:
For strict type control, consider using a render prop pattern instead of children:
interface ListProps<T> {
items: T[];
renderItem: (item: T) => React.ReactNode;
}
function List<T>({ items, renderItem }: ListProps<T>) {
return <ul>{items.map((item, i) => <li key={i}>{renderItem(item)}</li>)}</ul>;
}This provides better type inference and control than trying to type-check children.
React.FC expects children prop to be defined
React.FC no longer includes implicit children prop
Warning: You provided a `selected` prop to a form field without an `onChange` handler
You provided a 'selected' prop without an onChange handler
Failed to load source map from suspense chunk
How to fix "Failed to load source map from suspense chunk" in React
Prop spreading could cause security issues
Prop spreading could cause security issues
React Hook useCallback has a missing dependency: 'variable'. Either include it or remove the dependency array react-hooks/exhaustive-deps
React Hook useCallback has a missing dependency