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 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>
);
};
`
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>
)
);
})}
</>
);
}
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.
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.
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} />
}
}