Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React route render attribute type challenge typescript

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: enter image description here

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<...>'
like image 977
rustyBucketBay Avatar asked Nov 15 '25 05:11

rustyBucketBay


1 Answers

RouteComponentProps

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

Location

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

Edit: render vs. component

There 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.

  1. We can access it through the injected props in the component. This is exactly what we had before, but it is allowed on 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} />;
};
  1. We can access it through the router context by using the 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} />;
}
like image 169
Linda Paiste Avatar answered Nov 17 '25 21:11

Linda Paiste



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!