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.
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>
);
};
I've been digging around SO and the web at large for a solution, but I can't seem to nail it.
I have two components, Link and Button. Long story short: they are wrappers for <a> and <button> elements, but with the added options such as chevrons on the right-side, icons on the left-side, full-width mode, etc.
Here is what I have so far (and here's the code running on typescriptlang.org/play):
type Variant = "primary" | "secondary" | "tertiary";
interface CommonProps {
variant?: Variant;
showChevron?: boolean;
icon?: IconDefinition;
fullWidth?: boolean;
small?: boolean;
}
interface LinkOnlyProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
href: string;
}
interface ButtonOnlyProps
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
onClick: React.MouseEventHandler<HTMLButtonElement>;
}
export type LinkProps = CommonProps & LinkOnlyProps;
export type ButtonProps = CommonProps & ButtonOnlyProps;
export const Link = (props: LinkProps) => {
const {
children,
showChevron,
icon,
fullWidth,
variant,
small,
className,
...rest
} = props;
const { classes } = initButton(props);
return (
<a className={classes} {...rest}>
<Content {...props} />
</a>
);
};
export const Button = (props: ButtonProps) => {
const {
children,
showChevron,
icon,
fullWidth,
variant,
small,
className,
...rest
} = props;
const { classes } = initButton(props);
return (
<button className={classes} {...rest}>
<Content {...props} />
</button>
);
};
I've tried extracting the common logic for the Link and Button components into a single Component, however when I spread the ...rest props I get TypeScript yelling at me. From the error, it seems because I haven't been able to account for the possibility of <a> props being spread on to a <button> element and vice-versa.
I wanted to keep Link and Button as separate components, rather than specifying the type as a prop, so that the intentionality of the developer is clear when the components are being implemented.
Is there any possibility of extracting that common logic into a central component that both Link and Button can simply act as wrappers for? For example:
export const Link = (props: LinkProps) => {
return <Component element="a" {...props} />;
}
export const Button = (props: ButtonProps) => {
return <Component element="button" {...props} />;
}
Was able to work around the type assertion using as any when spreading the rest of my props:
return (
<Element className={classes} {...(rest as any)}>
<Content {...props} />
</Element>
);
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
I'm creating an element(Card component) using forwardRef but types mismatch when using it.
Here is the code of Card component
export const Card = React.forwardRef((
{ fluidHeight, className, ...props }:
{ fluidHeight?:boolean, className?:string, props?:any }, ref?:any) => (
<div
{...props}
ref={ref}
className={clsx(
"card card-custom gutter-b",
{ "card-height-fluid": fluidHeight },
className
)}
/>
));
Here is the code using Card component
<Card>
<CardHeader title="List">
<CardHeaderToolbar>
<button
type="button"
className="btn btn-primary"
onClick={UIProps.addButtonClick}
>
Add New
</button>
</CardHeaderToolbar>
</CardHeader>
<CardBody>
<DevicesFilter />
{UIProps.ids.length > 0 && <Grouping />}
<DevicesTable />
</CardBody>
</Card>
In the above snippet using Card component, compiler show type error on and hoving over it show following error.
Type '{ children: Element[]; }' has no properties in common with type 'IntrinsicAttributes & { fluidHeight?: boolean; className?: string; props?: any; } & RefAttributes'
I know it's a problem of forwardRef in typescript but don't know how to fix it.
Edit
I found a hack like this
Card component
export const Card = React.forwardRef((
{ children, fluidHeight, className, ...props }:
{children:JSX.Element[], fluidHeight?:boolean, className?:string, props?:any }, ref?:any) => (
<div
{...props}
ref={ref}
className={clsx(
"card card-custom gutter-b",
{ "card-height-fluid": fluidHeight },
className
)}
>
{/* {children} in case of forwardRef children elements are automatically passed from calling
component and should be used like this */}
</div>
));
But as per my assumption in this case when I don't need to pass any children elements for rendering inside Card component, I should use something else than forwardRef but don't know what :)
Please Help! Thanks
For children type, you shouldn't use JSX.Element or JSX.Element[]. Usually the convention is to use React.ReactNode. It includes the 2 above types and all other valid types for children like string or null.
So I'd change the props definition to:
type YourProps = { fluidHeight?:boolean, className?:string, props?:any };
type CardProps = React.PropsWithChildren<YourProps>;
export const Card = React.forwardRef((
{ fluidHeight, className, ...props }: CardProps, ref?:any) => (
<div
{...props}
ref={ref}
className={clsx(
"card card-custom gutter-b",
{ "card-height-fluid": fluidHeight },
className
)}
/>
));
where React.PropsWithChildren<T> is a utility type that includes children as React.ReactNode type. Here is the definition
type PropsWithChildren<P> = P & { children?: ReactNode };
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} />
}
}