Component cannot be used as a JSX component - javascript

I have a React component like this
import React from 'react';
import { ECOTileSummary } from './ECOTileSummary';
import { TileSummary } from './TileSummary';
interface SuperTileSummaryProps {
date?: string;
location: string;
link: string;
title: string;
nextVisit?: string | null;
tileEntity?: string;
}
export const SuperTileSummary = ({
date,
location,
link,
title,
nextVisit,
tileEntity,
}: SuperTileSummaryProps) => {
const chooseRegularTileByEntity = () => {
switch (tileEntity && tileEntity) {
case 'EmailCampaign':
return <ECOTileSummary date={date} link={link} location={location} title={title} />;
default:
return (
<TileSummary
nextVisit={nextVisit}
date={date}
link={link}
location={location}
title={title}
/>
);
}
};
return chooseRegularTileByEntity;
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
and I am calling it inside another component like this
import { SuperTileSummary} from './SuperTileSummary'
export const Wrapper = () => {
return(
<div>
<SuperTileSummary
nextVisit={nextVisit}
date={date}
link={link}
location={location}
title={title}
tileEntity={tileEntity}
/>
</div>
);
};
and I am getting an error like this: 'SuperTileSummary' cannot be used as a JSX component.
Its return type '() => JSX.Element' is not a valid JSX element.
Type '() => Element' is missing the following properties from type 'Element': type, props, keyts(2786)'.
I am not sure what am I doing wrong here since I'm rendering the component.

You can call chooseRegularTileByEntity function to get the returned value:
return chooseRegularTileByEntity();
or use it as component:
const ChooseRegularTileByEntity = () => {
//...
};
return <ChooseRegularTileByEntity />

The error message, 'SuperTileSummary' cannot be used as a JSX component. Its return type '() => JSX.Element' is not a valid JSX element., suggests that you're giving back a function that returns a JSX element where it expects a JSX element itself.
I believe the fix is to change return chooseRegularTileByEntity; to return chooseRegularTileByEntity(); The former is the function that returns JSX, the latter is the returned JSX itself

Related

How to type property based on other prop with Typescript and React?

How to make an intelligent prop type? I have Alert component which has some actions. Those actions can be clickable, there are some different components like Button or Link.
I would like to achieve something like this:
<Alert actions={[{ component: Link, props: { /* here only Link props available */ } }]} />
and
<Alert actions={[{ component: Button, props: { /* here only Button props available */ } }]} />
So props property should determine its type based on component property. Is this possible? I do not want to add any additional generics like
<Alert<ButtonProps> ... />
it should be "intelligent" and do it automatically
You can do this by generics, but it can get a little bit complicated: you need to explicit start which components are to be accepted by <Alert> via its prop type:
interface AlertAction<TFunctionalComponent extends FC<any>> {
component: TFunctionalComponent;
props: ComponentPropsWithoutRef<TFunctionalComponent>;
}
interface Props {
actions: Array<AlertAction<typeof Link | typeof Button>>;
}
export const Alert: FC<Props> = ({ actions }) => {
// Alert component body here
};
However I do see this as an anti-pattern: instead of splitting the component name and props into two separate keys, what if you simply let actions accept an array of ReactElement? i.e.:
interface Props {
actions: ReactElement[];
}
const Alert: FC<Props> = ({ actions }) => {
return <div>
{actions.map(action => <>{action}</>)}
</div>;
};
const MyApp: FC = () => {
return (
<>
{/* Will work */}
<Alert actions={[<Link {...linkProps} />]} />
<Alert actions={[<Button {...buttonProps} />]} />
</>
);
};
If you need to update the props or inject some custom child node into these elements, then you can take advantage of React.cloneChildren:
const Alert: FC<Props> = ({ actions }) => {
return (
<div>
{actions.map((action) => (
<>
{cloneElement(action, {
children: <>Custom child node for action elements</>,
})}
</>
))}
</div>
);
};

Element type is invalid: expected a string (for built-in components) or a class/function. while trying to render a component

`
Unhandled Runtime Error
Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
Check the render method of DashboardCards.
`
Im trying to have icons and labels in a common file constants/drawer
import DashboardIcon from "#mui/icons-material/Dashboard";
export const MainMenuItems = [
{
label: "Dashboard",
icon: <DashboardIcon />,
redirectLink: "/",
},
];
and in the main.js file importing them and trying to map them.
Here i want to pass some props to Icon like <Icon size="small"/>.
import { MainMenuItems } from "../constants/drawer.js";
const MainContainer=()=>{
return (
<>
{MainMenuItems.map(({label,icon:Icon}) => {
return (
<div>
<Icon size="small"/>
</div>
)
);
})}
</>
)
}
The Icon variable holds the component, you just need to execute it in the main.js file.
import { MainMenuItems } from "../constants/drawer.js";
const MainContainer = () => {
return (
<>
{MainMenuItems.map(({ label, icon: Icon }) => {
return <div>{Icon}</div>;
})}
</>
);
};
UPDATE
If you want to pass props to the component, I suggest you to just save the component reference to the icon property.
import Drawer from "#mui/material/Drawer";
export const MainMenuItems = [
{
label: "Dashboard",
icon: DashboardIcon,
redirectLink: "/",
},
];
main.js
import { MainMenuItems } from "../constants/drawer.js";
const MainContainer = () => {
return (
<>
{MainMenuItems.map(({ label, icon: Icon }) => {
return <div><Icon size="small" /></div>;
})}
</>
);
};
You need to write import statement for <DashboardIcon /> in constants/drawer.js
import DashboardIcon from '#mui/icons-material/Dashboard';
export const MainMenuItems = [
{
label: "Dashboard",
icon: <DashboardIcon />,
redirectLink: "/",
},
];
In main.js, we have to execute Icon as a value Icon have component in it.
import { MainMenuItems } from "../constants/drawer.js";
const MainContainer = () => {
return (
<>
{MainMenuItems.map(({label, icon: Icon}) => {
return (
<div>
{Icon}
</div>
)
);
})}
</>
);
}

Can't de-structure props from getStaticProps in NextJS?

I am building an app with Next JS and typescript. I am trying to call data from an api with getStaticProps, and then de-structure the returned props. For some reason, I can't get the props to de-structure.
Here is my getStaticProps function.
export async function getStaticProps() {
const projects = await client.fetch(groq`
*[_type == "project"]{
name, url, description, image, tools[]->{name,image}
}`);
const tools = await client.fetch(groq`*[_type == "techtool"]{
name, featured, url, image
}`);
return {
props: {
projects,
tools,
},
};
}
I then am trying to pass projects and tools to my page like so.
const Home: NextPage = ({projects, tools}) => {
console.log(props);
return (
<React.Fragment>
<Nav />
<Intro />
<Contact />
<Sidebar />
<Footer />
</React.Fragment>
);
};
export default Home;
However, I am getting the following error when I do this.
"Property 'projects' does not exist on type '{ children?: ReactNode; }'."
"Property 'tools' does not exist on type '{ children?: ReactNode; }'."
Do I need to somehow apply an interface to the props? What am I doing wrong?
If I log props to the console without de-structuring, it returns the two arrays it should.
Thanks in advance!
Annotate the props:
interface IHomeProps {
projects: whatGoesHere;
tools: whatGoesHere;
children?: ReactNode; // import from react if needed
}
const Home: NextPage = ({projects, tools}: IHomeProps) => {
You should try use pageProps in your page like this:
const Home: NextPage = ({ pageProps: {projects, tools} }) => {
console.log(projects, tools);
return (
<React.Fragment>
<Nav />
<Intro />
<Contact />
<Sidebar />
<Footer />
</React.Fragment>
);
};
This is how you can get the props from getStaticProps in your page.

Why is my number coming up as an object when passed in the prop?

I have a component FooterScroll. And the main page is TvIndex.
This is the FooterScroll Component
const FooterScroll = (Id: number) => {
const { event } = useEvent(Id);
console.log('Id', Id);
return (
<Marquee speed={85}>
<Grid container>
{event?.notices.map((notice: EventNotice) => {
if (notice.approved) {
return (
<>
<Grid item px={4} key={notice.id}>
<CampaignIcon />
</Grid>
<Grid item alignItems="center">
{notice.body}
</Grid>
</>
);
}
})}
</Grid>
</Marquee>
);
};
export default FooterScroll;
This is the TvIndex Page
import FooterScroll from 'components/pages/members/tv/scrolling-footer';
const TvIndex: React.FC = () => {
const classes = useStyles();
return (
<div className={classes.root}>
<div className={classes.scrollFooter}>
<FooterScroll Id={39324} />
</div>
</div>
);
};
export default TvIndex;
For some reasons that I don't understand, when I pass in this any id number as a prop in my main component for FooterScroll, it gives me this error: Type '{Id: number;}' is not assignable to type 'number'.
Then I decided to console log it inside FooterScroll, and it looks like after the id gets passed in TvIndex, it passes inside the FooterScroll component as an Object, like so: Id > Object { Id: 39324 }.
How can I solve this?
The argument passed to a React component is an object, whose properties are the props passed down by the caller. For example
<FooterScroll Id={39324} />
results in a prop object of:
{
Id: 39324
}
React doing it this way allows for easily adding additional props, or properties to the props object, without changing the function signature much:
<FooterScroll Id={39324} someOtherProp="foo" />
results in a prop object of:
{
Id: 39324,
someOtherProp: 'foo'
}
So, you need to change the function definition to
const FooterScroll = ({ Id }: { Id: number }) => {
or do
const FooterScroll = (props: { Id: number }) => {
const { Id } = props;
Your arguments to the FooterScroll are wrong. Try this:
const FooterScroll = ({ Id }: { Id: number }) => {
...
}
It should work.

Specify specific props and accept general HTML props in Typescript React App

I have a React Wrapper Component, that accepts some props, but forwards all others to the child component (especially relevent for native props like className, id, etc.).
Typescript complains, however, when I pass native props. See error message:
TS2339: Property 'className' does not exist on type
'IntrinsicAttributes & IntrinsicClassAttributes< Wrapper > & Readonly< {
children?: ReactNode; }> & Readonly< WrapperProps>'.
How can I get a component with specific props that also accepts native props (without accepting any props and giving up on type checking)?
My code looks like this:
interface WrapperProps extends JSX.IntrinsicAttributes {
callback?: Function
}
export class Wrapper extends React.Component<WrapperProps>{
render() {
const { callback, children, ...rest } = this.props;
return <div {...rest}>
{children}
</div>;
}
}
export const Test = () => {
return <Wrapper className="test">Hi there</Wrapper>
}
FYI: I found a similar question here, but the answer basically gives up type checking, which I want to avoid: Link to SO-Question
We can have a look at how div props are defined:
interface IntrinsicElements {
div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>;
}
If we use React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement> as the base type we will have all properties of div. Since DetailedHTMLProps just adds ref to React.HTMLAttributes<HTMLDivElement> we can use just this as the base interface to get all div properties:
interface WrapperProps extends React.HTMLAttributes<HTMLDivElement> {
callback?: Function
}
export class Wrapper extends React.Component<WrapperProps>{
render() {
const { callback, children, ...rest } = this.props;
return <div {...rest}>
{children}
</div>;
}
}
export const Test = () => {
return <Wrapper className="test">Hi there</Wrapper> // works now
}
JSX.IntrinsicElements has this info, e.g.
const FooButton: React.FC<JSX.IntrinsicElements['button']> = props => (
<button {...props} className={`foo ${props.className}`} />
)
// alternative...
const FooButton: React.FC<React.PropsWithoutRef<
JSX.IntrinsicElements['button']
>> = props => <button {...props} className={`foo ${props.className}`} />
discovered this in the react-typescript-cheatsheet project.
Have a look at ComponentProps, ComponentPropsWithRef, and ComponentPropsWithoutRef - this will accept a generic input that can be "div", "button", or any other component. It will include react specific props such as className as well:
import React, {
forwardRef,
ComponentPropsWithoutRef,
ComponentProps,
ComponentPropsWithRef
} from "react";
const ExampleDivComponent = forwardRef<
HTMLDivElement,
ComponentPropsWithoutRef<"div">
>(({ children, ...props }, ref) => {
return (
<div {...props} ref={ref}>
{children}
</div>
);
});
<ExampleDivComponent
className=""
style={{ background: "green" }}
tabIndex={0}
onTouchStart={() => alert("touched")}
/>;
const ExampleButtonComponent: React.FC<ComponentProps<"button">> = ({
children,
...props
}) => {
return <button {...props}>{children}</button>;
};
<ExampleButtonComponent onClick={() => alert("clicked")} />;
A co-worker of mine figured it out. Sharing here for broader visibility:
interface ComponentPropTypes = {
elementName?: keyof JSX.IntrinsicElements; // list of all native DOM components
...
}
// Function component
function Component({
elementName: Component = 'div',
...rest,
// React.HTMLAttributes<HTMLOrSVGElement>) provides all possible native DOM attributes
}: ComponentPropTypes & React.HTMLAttributes<HTMLOrSVGElement>)): JSX.Element {
return <Component {...rest} />;
}
// Class component
class Component extends React.Component<ComponentPropTypes & React.HTMLAttributes<HTMLOrSVGElement>> {
render() {
const {
elementName: Component,
...rest,
} = this.props;
return <Component {...rest} />
}
}

Categories