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

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) => {

Related

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

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.

React - Type '(studentIds: Student[])' is missing the following properties from type 'Stduent[]' length, pop, push, concat, and 26 more. [2740]

I'm getting an error that says:
Type '(studentIds: string[]) => Promise<void>' is missing the following properties from type 'string[]':
pop, push, concat, join, and 28 more.ts(2740)
StudentModal.tsx(143, 27): The expected type comes from property 'students' which is declared here on
type 'IntrinsicAttributes & Props & { children?: ReactNode; }'
My component has props defined as:
interface Props {
isOpen: boolean;
students: Array<Student['id']>;
onStudents: () => void;
onClose: () => void;
}
It is called as:
<StudentModal
isOpen={isOpenStudentsModal}
students={getStudents}
onStudents={studentCallback}
onClose={studentToggle}
/>
whereas getStudents is defined as:
export const getStudents = async(studentIds: Array<Student['id']>) : Promise<void> =>
{
// service call to get student ids
};
As far as I can see, parameter list on getStudents matches the one on the Props interface. What am I doing wrong here?
Your interface defined students as
students: Array<Student['id']>;
but when you render the component, you are passing in the function of getStudents
students={getStudents}
so the type you passed into the component is (studentIds: string[]) => Promise<void> and not string[].
What the error is saying is that the prop passed in do not have array methods like pop/push.
Since getStudents is a async function, you probably need to store the result in the state, then pass that state into the Model.
For example,
function Main() {
const [studentIds, setStudentIds] = useState<string[]>([]);
useEffect(() => {
getStudents().then(studentIds => setStudentIds(studentIds))
}, []);
return (
<StudentModal
students={studentIds}
...
/>
);
}

Type argument at moment of use

I'm trying to build a generic tool for handling the response to React Query useQuery requests in my app, and build this out using TypeScript, so that the data is correctly typed.
In a parent component, the use case would be something like this:
const queryResult = useQuery<MyResponseType, Error>('whatever');
return (
<ReactQueryLoader useQueryResult={queryResult}>
{data => {
// Data should be of MyResponseType type, but is unknown
return <pre>{JSON.stringify(data, null, 2)}</pre>;
}}
</ReactQueryLoader>
);
I tried defining this ReactQueryLoader as so:
import React from "react";
import { UseQueryResult } from "react-query";
import { Loading } from "./Loading";
interface ReactQueryLoaderProps {
useQueryResult: UseQueryResult<unknown, Error>;
handleError?: (error: Error) => void;
children: (data: unknown) => JSX.Element;
}
export const ReactQueryLoader = ({
useQueryResult,
handleError = error => {
console.error(error);
throw new Error(error.message);
},
children,
}: ReactQueryLoaderProps): null | JSX.Element => {
if (useQueryResult.isError) {
handleError(useQueryResult.error);
return null;
}
if (useQueryResult.isLoading) {
return <Loading />;
}
return children(useQueryResult.data);
};
But when I inspect the data passed to the child, instead of being the type of the expected response data, it is unknown. I'm having trouble getting this to work, as someone with a mid-low-level understanding of TypeScript. I thought I could apply the same pattern as in this answer to another question I had, but I can't figure out how to do it in this context.
I tried this:
interface ReactQueryLoaderProps {
// ...
children: <T extends unknown>(data: T) => JSX.Element;
}
But got this in the parent element:
(parameter) data: T extends unknown
I also tried:
interface ReactQueryLoaderProps {
useQueryResult: UseQueryResult<T extends unknown, Error>;
But that provoked two errors:
Under the T: Cannot find name 'T'.
Under the comma: '?' expected.
Is this possible to do?
The ReactQueryLoader function needs to be generic here, as well as the props type.
That might look something like this:
interface ReactQueryLoaderProps<T extends UseQueryResult<unknown, Error>> {
useQueryResult: T;
handleError?: (error: Error) => void;
children: (data: T['data']) => JSX.Element;
}
And
export function ReactQueryLoader<T extends UseQueryResult<unknown, Error>>({
useQueryResult,
handleError,
children,
}: ReactQueryLoaderProps<T>): null | JSX.Element {
//...
}
Here ReactQueryLoader is now a generic function that captures the query result type as T and passes that to the generic props type ReactQueryLoaderProps.
ReactQueryLoaderProps then uses that type as the type of useQueryResult, and pulls the ['data'] property from that type for the type of the children function parameter.
See playground

object parameters type definition in typescript

I am passing a component some props. One of this is a function that doesn't return anything. I have to pass two an object as parameter comosed by two booleans. How do I specify that those parameters are booleans?
const MyComponent: React.FC<{
onChange: ({ isFoo1, isFoo2 }) => void;
}> = ({ onChange }) => {
const [isFoo1, setIsFoo1] = useState<boolean>(false);
const [isFoo2, setIsFoo2] = useState<boolean>(false);
/*
* stuff...
*/
useEffect({
onChange({isFoo1, isFoo2})// <----- Type error
},[isFoo1, isFoo2])
}
The typerror has code 2345 and it appears because on onChange definition I haven't specified the type of the two parameters.. How can I do that?
Don't use destructuring in the type of the onChange function. Instead, just use a normal object parameter and type it as you would an object:
onChange: (obj: {isFoo1: boolean, isFoo2: boolean}) => void;
Playground link

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