Reputation: 175
In App.tsx I'm passing <Left />
and <Right />
to a an imported component called <SplitScreen />
Apparently the "children" prop needs to be explicitly typed since React 18. If I type it as React.Element[]
then SplitScreen.tsx
works but App.tsx
throws:
Type 'Element' is not assignable to type 'ElementType<any>'
If I type it as React.ReactNode
(which seems to be the consensus currently) App.tsx
works but SplitScreen.tsx
throws:
Type 'ReactNode' must have a '[Symbol.iterator]()' method that returns an iterator
And no, React.ReactNode[]
doesn't help, it just changes the error to:
Type 'ReactNode[] | undefined' must have a '[Symbol.iterator]()'
// package.json
{
//...
"dependencies": {
"@babel/preset-typescript": "^7.18.6",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@babel/plugin-transform-typescript": "^7.18.12",
"@types/react": "^18.0.17",
"@types/react-dom": "^18.0.6",
"@vitejs/plugin-react": "^2.0.1",
"typescript": "^4.6.4"
}
}
// App.tsx
const LeftHandComponent = () => {
return <h1 style={{ backgroundColor: 'blueviolet' }}>Left</h1>;
};
function App() {
return (
<SplitScreen {...appProps}>
<LeftHandComponent></LeftHandComponent>
<!-- Type 'Element' is not assignable to type 'ElementType<any>' -->
<RightHandComponent></RightHandComponent>
</SplitScreen>
);
}
// SplitScreen.tsx
interface SplitScreenProps {
// extends PropsWithChildren {
leftWeight: number;
rightWeight: number;
children?: React.ReactNode;
// children: React.ElementType[];
}
export const SplitScreen = ({ leftWeight, rightWeight, children }: SplitScreenProps) => {
// Type 'ReactNode' must have a '[Symbol.iterator]()' method that returns an iterator
const [Left, Right] = children; // HERE
return (
<Container>
<Pane weight={leftWeight}>
<Left></Left>
</Pane>
<Pane weight={rightWeight}>
<Right></Right>
</Pane>
</Container>
);
};
Upvotes: 0
Views: 4007
Reputation: 175
This is how I solved it; this entry had the last piece of the puzzle The Components need to be wrapped!
// App.jsx
const LeftHandComponent = ({name}: {name: string}) => {
return <h1 style={{ backgroundColor: 'blueviolet' }}>{name}</h1>
};
const appProps = {
leftWeight: 1,
rightWeight: 3,
};
function App() {
return <SplitScreen {...appProps}>
<LeftHandComponent name="Left" />
<RightHandComponent />
</SplitScreen>
}
// SplitScreen.tsx
interface SplitScreenProps {
leftWeight: number;
rightWeight: number;
children: React.ReactNode;
}
export const SplitScreen = ({ leftWeight, rightWeight, children }: SplitScreenProps) => {
const [Left, Right] = React.Children.toArray(children);
return (
<Container>
<Pane weight={leftWeight}>
<React.Fragment>{Left}</React.Fragment>
</Pane>
<Pane weight={rightWeight}>
<React.Fragment>{Right}</React.Fragment>
</Pane>
</Container>
);
};
Upvotes: 0
Reputation: 66123
That is because ReactNode
is not necessarily iterable (it can potentially be undefined, among its many other union types), so attempting to spread it into an array as: const [Left, Right] = children
.
This can be remedied by using React.Children.toArray(children)
before attempting to spread them:
const [Left, Right] = React.Children.toArray(children);
Another suggestion is that instead of trying to treat children
as array, perhaps a more explicit way to pass these nodes in is not to rely on children
prop, but pass them explicitly as props, e.g.:
interface SplitScreenProps {
leftWeight: number;
rightWeight: number;
leftNode: React.ReactNode;
rightNode: React.ReactNode;
}
Then the consuming parent of the component can just do this:
<SplitScreen
{...appProps}
leftNode={<LeftHandComponent />}
rightNode={<RightHandComponent />}>
Upvotes: 1