I have some component props called project passed through a Link to a Route like this (my project object goes in the state extended property):
<Link
to={{
pathname: path,
state: {
project,
},
}}
key={project.id}
>
<ProjectSummary project={project} deleteCallback={projectDelete}/>
</Link>
So then its received in the route and can be passed to the linked component like this:
<Route
path='/project/:id'
render={({ location }:any) => { //TYPE CHALLENGE HERE
const { state } = location;
return <ProjectDetails project={state.project} />
}}
/>
Find the any type with the //TYPE CHALLENGE HERE comment .I tried lots of types of 'react-router' and 'react-router-dom' and it is impossible to find the matching type.
The closest seem this one:
export interface RouteComponentProps<
Params extends { [K in keyof Params]?: string } = {},
C extends StaticContext = StaticContext,
S = H.LocationState
> {
history: H.History<S>;
location: H.Location<S>;
match: match<Params>;
staticContext?: C;
}
As the component receives all the route params from this interface, but I extend the location where my project object is passed in.
In case it is of any relevance, this is the type of the project object I pass in:
export interface IFirebaseProject {
id: string,
authorFirstName: string,
authorId: string,
authorLastName: string
content: string
createdAt: firebase.firestore.Timestamp //firebase timestamp
title: string
}
I post also the error I get with what I believe is the closest approach:
render={({ location }:RouteComponentProps<any, StaticContext, IFirebaseProject>) => {}
No overload matches this call.
Overload 1 of 2, '(props: RouteProps | Readonly<RouteProps>): Route<RouteProps>', gave the following error.
Type '({ location }: RouteComponentProps<any, StaticContext, IFirebaseProject>) => JSX.Element' is not assignable to type '(props: RouteComponentProps<any, StaticContext, unknown>) => ReactNode'.
Types of parameters '__0' and 'props' are incompatible.
Type 'RouteComponentProps<any, StaticContext, unknown>' is not assignable to type 'RouteComponentProps<any, StaticContext, IFirebaseProject>'.
Type 'unknown' is not assignable to type 'IFirebaseProject'.
Overload 2 of 2, '(props: RouteProps, context: any): Route<RouteProps>', gave the following error.
Type '({ location }: RouteComponentProps<any, StaticContext, IFirebaseProject>) => JSX.Element' is not assignable to type '(props: RouteComponentProps<any, StaticContext, unknown>) => ReactNode'.ts(2769)
index.d.ts(89, 5): The expected type comes from property 'render' which is declared here on type 'IntrinsicAttributes & IntrinsicClassAttributes<Route<RouteProps>> & Readonly<RouteProps> & Readonly<...>'
index.d.ts(89, 5): The expected type comes from property 'render' which is declared here on type 'IntrinsicAttributes & IntrinsicClassAttributes<Route<RouteProps>> & Readonly<RouteProps> & Readonly<...>'
Edit:
Updated screenShot of the log error in render:

Text full text of the error with the render={({ location }: { location: Location<{ project: IFirebaseProject }> }) => { attempt:
No overload matches this call.
Overload 1 of 2, '(props: RouteProps | Readonly<RouteProps>): Route<RouteProps>', gave the following error.
Type '({ location }: { location: Location<{ project: IFirebaseProject;}>; }) => JSX.Element' is not assignable to type '(props: RouteComponentProps<any, StaticContext, unknown>) => ReactNode'.
Types of parameters '__0' and 'props' are incompatible.
Type 'RouteComponentProps<any, StaticContext, unknown>' is not assignable to type '{ location: Location<{ project: IFirebaseProject; }>; }'.
Types of property 'location' are incompatible.
Type 'Location<unknown>' is not assignable to type 'Location<{ project: IFirebaseProject; }>'.
Type 'unknown' is not assignable to type '{ project: IFirebaseProject; }'.
Overload 2 of 2, '(props: RouteProps, context: any): Route<RouteProps>', gave the following error.
Type '({ location }: { location: Location<{ project: IFirebaseProject;}>; }) => JSX.Element' is not assignable to type '(props: RouteComponentProps<any, StaticContext, unknown>) => ReactNode'.ts(2769)
index.d.ts(89, 5): The expected type comes from property 'render' which is declared here on type 'IntrinsicAttributes & IntrinsicClassAttributes<Route<RouteProps>> & Readonly<RouteProps> & Readonly<...>'
index.d.ts(89, 5): The expected type comes from property 'render' which is declared here on type 'IntrinsicAttributes & IntrinsicClassAttributes<Route<RouteProps>> & Readonly<RouteProps> & Readonly<...>'
You are so close! The S argument of RouteComponentProps controls the state type for both the location and the history props, so that is what you want to use.
The mistake is that you are setting S to IFirebaseProject which means that the state is itself an IFirebaseProject. But in reality the state looks like {project}. It is an object with a key project that has a value of IFirebaseProject. So the correct type for S is {project: IFirebaseProject}.
With all of the types properly set, it is this:
render={({ location }: RouteComponentProps<{id: string}, StaticContext, {project: IFirebaseProject}>) => {
But location doesn't use those other two generics, so this is fine:
render={({ location }: RouteComponentProps<any, any, {project: IFirebaseProject}>) => {
Personally I would just declare a type for the location property since that is the only property of the RouteComponentProps that you actually need. I don't like using types which rely on generics that aren't used.
The underlying Location type comes from the history package which is a dependency of react-router. Its types are imported into the react-router-dom types with import * as H from "history", so that's why you see H.Location. But you can import it directly:
import {Location} from "history";
And use it like this:
render={({ location }: {location: Location<{project: IFirebaseProject}>}) => {
Typescript Playground Link
render vs. componentThere is something strange going on in your CodeSandbox as I do see the error but I cannot reproduce it in isolation in the TS Playground. Rather than banging our heads against the wall let's just use a slightly different approach that circumvents the error.
The component prop has a much more permissive type than render because it allows the props type to be either RouteComponentProps<any> (what we expect) or any (literally anything). Just switching the prop name from render to component makes it suddenly work! We don't want to define a component inline because it will be recreated on each render, but we can define it externally.
We will now use the component prop for the Route instead of using a render function:
<Route path="/project/:id" component={ProjectScreen} />
There are two options for accessing the location in ProjectScreen which both seem to work for me.
component and not render due to the any type on the props.const ProjectScreen = ({ location }: { location: Location<{ project: IFirebaseProject }>; }) => {
const { state } = location;
return <ProjectDetails project={state.project} />;
};
useLocation hook. This hook is generic depending on state, so we can specify the state type when we call it. We define a component that takes no props but renders the appropriate ProjectDetails based on the location from the hook.const ProjectScreen = () => {
const { state } = useLocation<{ project: IFirebaseProject }>();
return <ProjectDetails project={state.project} />;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With