How to pass props to react component in TypeScript? [duplicate] - reactjs

This question already has an answer here:
Type of props is not assignable to type 'string'
(1 answer)
Closed last month.
So I have a react component:
function Navbar(isDark: boolean, toggle: () => void) {
...
}
Now I want to use this component in another component like so:
function App() {
return <div> <Navbar isDark={someBoolValue} toggle={someFunc} /> </div>
}
However I get this error:
Type '{ isDark: boolean; toggle: () => void; }' is not assignable to type 'IntrinsicAttributes & boolean'.
How can I fix this?

Props need to be deconstructed because they are passed as objects. You can adjust your NavBar as follows
func Navbar({isDark, toggle}:{isDark:boolean,toggle:() => void}) {
...
}
You could also create an interface
interface IProps {
isDark: boolean;
toggle: () => void;
}
func Navbar({isDark, toggle}:IProps) {
...
}
CodeSandBox : https://codesandbox.io/s/dawn-tdd-g16din?file=/src/Navbar.tsx
This is worth a read : https://stackoverflow.com/a/65948871/15474532

To expound upon the other answer, I would type it as such, which is much more explicit and strongly typed.
type NavbarProps = {
isDark: boolean;
toggle: () => void;
}
const Navbar: React.FC<NavbarProps> = ({ isDark, toggle}) => {
...
}

React is expecting a functional component to only have one parameter, which is a object containing the props. To immediately fix you problem, you could change this to
function Navbar(props: {isDark: boolean, toggle: () => void}) {
...
}
and then access the props in the function as props.isDark or props.toggle. For readability, you may also consider writing this as
interface NavbarProps {
isDark: boolean;
toggle: () => void;
}
function Navbar({isDark, toggle}: NavbarProps) {
...
}
Generally good practice to put component props in interfaces in the same spot in every file for quick reference.

Related

Type 'unknown' is not assignable to other type, when strict mode is true

I have type error.
Exists child component Pagination with type of props:
interface BaseProps {
url: string;
rowKey: string;
renderItem: (item: unknown) => React.ReactNode;
params?: Record<string, unknown>;
foundTextSuffix?: string;
separated?: boolean;
useLaggy?: boolean;
}
In parent component function renderItem is passed to child component Pagination. This function needed to render other nested components in different places.
<Pagination
url={API.service('search')}
rowKey="id"
foundTextSuffix=" services"
renderItem={({id, shortTitle, organizationTitle}: ServiceExtended) => (
<ListItem
title={<Link href={{pathname: '/services/[serviceId]', query: {serviceId: id}}}>{shortTitle}</Link>}
subTitle={organizationTitle}
/>
)}
params={frozen}
/>
Argument of function renderItem has type unknown in type of Pagination component, strict mode is true. When I try to pass a function in props of Pagination component with argument type ServiceExtended, for example, there is type error:
Type 'unknown' is not assignable to type 'ServiceExtended'
I can't list all possible types in argument of renderItem, because there are a lot of them, there will be new ones in the future and this approach doesn't work with strict mode.
Please, help to find solution
I wrote simple example of my case below. This type error is occurring only with strict mode
type childPropType ={
render: (data: unknown) => JSX.Element
}
type parentPropType = {
id: number,
greeting: string
}
const data: parentPropType = {
id: 1,
greeting: 'Hello'
}
const Child = (props: childPropType) => {
const {render} = props;
return (
<div>{render(data)}</div>
)
}
const Parent = () => {
return (
<div>
<Child
render={
({id, greeting}: parentPropType) => <div>{greeting}</div>
}
/>
</div>
)
}

Typescript how to make a fucntion optional when its passed via props

I'm trying to pass a function through props but I'd like it to be optional. However, if I try to make it optional in the props interface by putting a "?" in front of the function, it gives me this error:
'propsFunction', which lacks return-type annotation, implicitly has an 'any' return type.
Is there any way to make a function optional to pass via props?
interface Props {
users: Array<User> | undefined;
propsFunction(userTo:string): void;
}
const Component: React.FC<Props> = (props) => {
You can use arrow function syntax to define the type
interface Props {
users: Array<User> | undefined;
propsFunction?: (userTo: string) => void;
}
const Component: React.FC<Props> = (props) => {

Difference in typing when passing spread props vs html-style attributes

Summary:
Typescript gives an error when passing a prop through standard propName={propValue} but not when using spread props {...{propName: propValue}}
To clarify: I know how to solve this. I could add it explicitly to the IconButtonProps type; I could also add & React.HTMLAttributes<HTMLButtonElement> so I don't have to iterate all button props that I want to be able to pass through; I could also just use spread props everywhere (which is what I do 90% of the time anyway).
However, it seems like these two components should be the same. I'm looking to understand why these two supposedly equivalent ways of passing props behave differently in typescript.
Code Example
Playground Link
import React from "react";
type IconButtonProps = {
className?: string;
"aria-label": string;
};
const IconButton = ({ className, ...otherProps }: IconButtonProps) => {
return (
<button {...otherProps} className={className}>
pretend I am an icon
</button>
);
};
const ComponentWithAttributes = () => {
const handleClick = () => {
console.log("yooo");
};
return <IconButton aria-label="manage section" onClick={handleClick} />;
};
const ComponentWithSpreadProps = () => {
const handleClick = () => {
console.log("yooo");
};
return (
<IconButton aria-label="manage section" {...{ onClick: handleClick }} />
);
};
ComponentWithAttributes gives this error:
Type '{ "aria-label": string; onClick: () => void; }' is not assignable to type 'IntrinsicAttributes & IconButtonProps'.
Property 'onClick' does not exist on type 'IntrinsicAttributes & IconButtonProps'.(2322)
ComponentWithSpreadProps has no errors
This is because of the structural typing nature of typescript. You can read more in the link provided.
When you spread an object, you don't really know its properties, or if it has excess properties, as some objects properties are inherited. Typescript does not catch errors on any other properties on the spread object.
example
var x = { d: 0 };
var y: { a: number, b: string } = {
a: 0,
b: "",
...x,
d: 100
};
Typescripts will raise an error when you try to assign d in y, but will allow spreading of x.
I think the problem is the missing "onClick" in your IconButtonProps type.
I just edit to:
type IconButtonProps = { className?: string; "aria-label": string; onClick: () => void };
and the error is gone
Regards!

Type '({ items }: PropsWithChildren<TodoProps>) => Element[]' is not assignable to type 'FunctionComponent<TodoProps>'

I'm learning Typescript-react and I'm stuck in this error Type '({ items }: PropsWithChildren<TodoProps>) => Element[]' is not assignable to type 'FunctionComponent<TodoProps>' and I am lost on this.
Complete error:
Type '({ items }: PropsWithChildren<TodoProps>) => Element[]' is not assignable to type 'FunctionComponent<TodoProps>'.
Type 'Element[]' is missing the following properties from type 'ReactElement<any, string | ((props: any) => ReactElement<any, string | ... | (new (props: any) => Component<any, any, any>)> | null) | (new (props: any) => Component<any, any, any>)>': type, props, key
Link for code: sandbox repo.
Error happens on the declaration of TodoList function within the TodoList.tsx file.
Any help is appreciated. Cheers!
Code:
import React from "react";
interface Todo {
id: number;
content: string;
completed: boolean;
}
interface TodoProps {
items: Todo[];
}
// v------v here is error
const TodoList: React.FC<TodoProps> = ({ items }) => {
return items.map((item: Todo) => <div key={item.id}>{item.id}</div>);
};
export default TodoList;
Yeah, the error may sound a bit confusing - in essence it says that you can only return a single ReactElement or its equivalent JSX.Element in the function component definition, enforced by React.FC type.
React Fragments solve this limitation, so you can write TodoList in the following manner:
interface TodoProps {
items: Todo[];
}
const TodoList: React.FC<TodoProps> = ({ items }) => (
<React.Fragment>
{items.map((item: Todo) => (
<div key={item.id}>{item.id}</div>
))}
</React.Fragment>
);
Short form:
const TodoList: React.FC<TodoProps> = ({ items }) => (
<>
{items.map(({ id }) => <div key={id}>{id}</div>)}
</>
);
By the way: With pure JS, both class and function components can return multiple elements in an array as render output. Currently, TS has a type incompatibility for returned arrays in function components, so Fragments provide a viable workaround here (in addition to type assertions).
I've encountered a similar error. Eventually I noticed that I'd renamed the file incorrectly from .js to .ts instead of to .tsx when converting a component to a FunctionComponent with TypeScript.
I also got this error when I was trying to return children props from my Loading component like below.
const { loading, children } = props;
return loading ? <p>Loading ... </p> : children;
Then i realize that React is expecting only one return value(1 parent component) from its render method. Therefore I wrapped children props with React.Fragment which is denoted by <></> and that solves my problem. Below is my Loadingcomponent sample, hope that helps someone else.
import { FunctionComponent } from "react";
interface ILoadingProps {
loading: boolean;
}
export const Loading: FunctionComponent<ILoadingProps> = (props) => {
const { loading, children } = props;
return loading ? <p>Loading ... </p> : <>{children}</>;
};
My problem was that I had allowed a "TypeScript Quick Fix" in VSCode to add async to the main functional component interface.
const Welcome: React.FC<TProps> = async (props) => {
After removing it,
const Welcome: React.FC<TProps> = (props) => {
things were back to normal.

TypeScript not inferring props from React.ComponentType

I have the following function which I want to use to take in a ComponentType and its props in order to allow me to inject those props along with the RouteComponentProps
const routeComponentFactory = <TProps extends {}>(
Component: React.ComponentType<TProps>,
props: TProps
) => {
return (routeProps: RouteComponentProps) => {
return <Component {...routeProps} {...props} />;
};
};
This function works correctly if I explicitly specify TProps, for example:
interface MyComponentProps { a: number; b: number; }
const MyComponent: React.FunctionComponent<MyComponentProps> = () => null;
routeComponentFactory<MyComponentProps>(MyComponent, {});
I get an error there for not providing a and b in the object.
However, if I remove the explicit <MyComponentProps>, the error goes away and I am allowed to call the function with an empty object.
How can I make TypeScript properly infer MyComponentProps?
If you enable strictFunctionTypes you will get an error:
Type 'FunctionComponent<MyComponentProps>' is not assignable to type 'FunctionComponent<{}>'.
There are two possible inference sites for TProps the arguments Component and props both contain the type parameter TProps so typescript tries to find a type that will make both sites happy. Since if TProps were {} both the argument {} and the props type MyComponentProps would be assignable to it typescript infers TProps to be {} in order to keep everyone happy.
The reason that under strictFunctionTypes you do get an error is that by default function type behave bivariantly (so a function (p: MyComponentProps) => JSX.Element is assignable to (p: {}) => JSX.Element). Under strictFunctionTypes function types behave contravariantly so such an assignment is disallowed.
The solution to get an error even without strictFunctionTypes is to decrease the priority of the props inference site, so the compiler picks what is good for Component and checks it against props. This can be done using an intersection with {}:
const routeComponentFactory = <TProps extends {}>(
Component: React.ComponentType<TProps>,
props: TProps & {}
) => {
return (routeProps: any) => {
return <Component {...routeProps} {...props} />;
};
};
interface MyComponentProps { a: number; b: number; }
const MyComponent: React.FunctionComponent<MyComponentProps> = () => null;
routeComponentFactory(MyComponent, {}); // Argument of type '{}' is not assignable to parameter of type 'MyComponentProps'
I cannot see it allowing me to call the function with an empty object but in my case it is complaining that the MyComponent prop is invalid.
It seems the problem is it is inferring the type from the second parameter - I guess because it is matching on the simplest type.
You could define your function like this to force it to infer the correct type:
const routeComponentFactory = <TProps extends {}>(
Component: React.ComponentType<TProps>
) => (
props: TProps
) => {
return (routeProps: RouteComponentProps) => {
return <Component {...routeProps} {...props} />;
};
};
And then call it like this:
routeComponentFactory(MyComponent)({});
And it should complain about the empty object correctly in this case.

Resources