Typescript challenge: Implementing a route render attribute in React with TypeScript

My component has props named project which are passed through a Link to a Route. Here's how it looks (the project object goes in the state extended property):

<Link
  to={{
    pathname: path,
    state: {
      project,
    },
  }}
  key={project.id}
>
  <ProjectSummary project={project} deleteCallback={projectDelete}/>
</Link>

Once this data is received in the route, it can be further passed to the linked component like so:

<Route
  path='/project/:id'
  render={({ location }:any) => { //TYPE CHALLENGE HERE
    const { state } = location;
    return <ProjectDetails project={state.project} /> 
  }}
/>

The challenge arises when trying to find the appropriate type for the any with the comment //TYPE CHALLENGE HERE. I've experimented with various types from 'react-router' and 'react-router-dom', but haven't been able to identify an exact match.

The closest relevant interface seems to be:

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;
}

This interface provides all the route params to the component, but I specifically extend the location where my project object is included. For reference, here is the type of the project object being passed in:

export interface IFirebaseProject {
  id: string,
  authorFirstName: string,
  authorId: string,
  authorLastName: string
  content: string
  createdAt: firebase.firestore.Timestamp //firebase timestamp
  title: string
}

Additionally, I am encountering an error with the following attempt:

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

Updated information about the log error can be found in the screenshot below:

Error message related to the render function:

Full error details with the updated

render={({ location }: { location: Location<{ project: IFirebaseProject }> }) => {
:

No overload matches this call.
  Overload 1 of 2, '(props: RouteProps | Readonly<RouteProps>): Route<RouteProps>', gave the following error...

Answer №1

RouteComponentProps

You're almost there! The S argument in RouteComponentProps determines the state type for both the location and history props, so that's what you should use.

The issue is setting S to IFirebaseProject, which means the state is an IFirebaseProject itself. In reality, the state is an object with a key project having a value of IFirebaseProject. So, the correct type for S is {project: IFirebaseProject}.

With all types correctly set, it looks like this:

render={({ location }: RouteComponentProps<{id: string}, StaticContext, {project: IFirebaseProject}>)  => {

However, since location doesn't use those other two generics, this works fine:

render={({ location }: RouteComponentProps<any, any, {project: IFirebaseProject}>)  => {

Location

I personally prefer declaring a type for the location property since that's the only property of RouteComponentProps needed. I avoid using types relying on unused generics.

The underlying Location type comes from the history package, a dependency of react-router. Its types are imported into the react-router-dom types using

import * as H from "history"
, which explains why you see H.Location. However, you can directly import it:

import {Location} from "history";

and use it like this:

render={({ location }: {location: Location<{project: IFirebaseProject}>})  => {

Typescript Playground Link

Edit: render vs. component

In your CodeSandbox, there seems to be a peculiar error that doesn't appear when isolated in the TS Playground. Instead of struggling, let's take a different approach that works around the error.

The component prop has a more flexible type than render because it allows the props type to be either RouteComponentProps<any> (expected) or any (anything). By simply switching from render to component, it miraculously works! We don't want to create a component inline due to being recreated on each render, but we can define it externally.

We will now use the component prop for the Route instead of a render function:

<Route path="/project/:id" component={ProjectScreen} />

There are two ways to access the location in ProjectScreen, both working well for me.

  1. We can access it through injected props in the component. This is similar to before but possible on component, not render due to the any type on 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 using the useLocation hook. This hook's generic depends on state, allowing us to specify the state type when calling it. We define a component taking no props but rendering the appropriate ProjectDetails based on the location from the hook.
const ProjectScreen = () => {
  const { state } = useLocation<{ project: IFirebaseProject }>;
  return <ProjectDetails project={state.project} />;
}

Answer №2

The RouteProps type is structured as follows:

export interface RouteProps {
    location?: H.Location;
    component?: React.ComponentType<RouteComponentProps<any>> | React.ComponentType<any>;
    render?: (props: RouteComponentProps<any>) => React.ReactNode;
    children?: ((props: RouteChildrenProps<any>) => React.ReactNode) | React.ReactNode;
    path?: string | string[];
    exact?: boolean;
    sensitive?: boolean;
    strict?: boolean;
}

In the render attribute, only the first argument is defined with RouteComponentProps, leaving the other two arguments to default values. The second argument is C, representing StaticContext, and the third argument is S, which leads to unknown through H.LocationState. Below is the structure of RouteComponentProps for more clarity:

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;
}

To fix this issue, it is advisable to define all three possible arguments that the render attribute can accept in RouteProps like so:

render?: (props: RouteComponentProps<any,any,any>) => React.ReactNode;
. This will prevent the second and third arguments of RouteComponentProps from defaulting automatically.

This sheds light on the 'forced' nature of types in the render attribute and the common error message encountered:

RouteComponentProps<any, StaticContext, unknown>' is not assignable to ...

If you spot any issues or have insights on types in an external library, feel free to share your comments or concerns. Your feedback is greatly appreciated!

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Passing an event from onSubmit in React without using lambdas

Within our current project, the tslint rule jsx-no-lambda is in place. When attempting to capture event from onSubmit, this is how I typically write my code: public handleLogin = (event: React.FormEvent<HTMLFormElement>) => { event.preventDe ...

We are in need of a provider for the Ionic Network native plugin

I have encountered an issue while trying to use Ionics native plugin "Network" as it fails due to a missing provider. To prevent any errors, I performed a fresh installation of Ionic along with the necessary dependencies: ionic cordova plugin add cordova- ...

Ways to transfer information from HTML form components to the Angular class

I am currently working with angular 9 and I have a requirement to connect data entered in HTML forms to the corresponding fields in my Angular class. Below is the structure of my Angular class: export interface MyData { field1: string, textArea1 ...

Utilizing Typescript version 1.5 alongside window.event.ctrlKey

When I need to debug, I occasionally check if the ctrl key is pressed for certain secret actions. This check may be included in any function, not necessarily an event handler itself (it could be a callback or an event handler). In my TypeScript code, I us ...

Anticipated the object to be a type of ScalarObservable, yet it turned out to be an

When working on my Angular project, I utilized Observables in a specific manner: getItem(id): Observable<Object> { return this.myApi.myMethod(...); // returns an Observable } Later, during unit testing, I successfully tested it like so: it(&apos ...

Employing Typescript types in array notation for objects

Can someone please help me decipher this code snippet I found in a file? I'm completely lost as to what it is trying to accomplish. const user = rowData as NonNullable<ApiResult["getUsers"]["data"][number]["users"]> ...

The type 'TaskListProps[]' cannot be assigned to type 'TaskListProps'

I'm struggling with handling types in my TypeScript application, especially with the TaskListProps interface. export default interface TaskListProps { tasks: [ { list_id: string; title: string; description: string; status ...

Utilizing absolute path in Typescript: A comprehensive guide

I am currently working on a project written in Typescript running on NodeJS. Within the project, I have been using relative paths to import modules, but as the project grows, this approach is becoming messy. Therefore, I am looking to convert these relativ ...

TypeScript equivalent to Python's method for removing non-whitespace characters is achieved by

I understand that I can utilize .trim() to eliminate trailing spaces Is there a method to trim non-space characters instead? In [1]: str = 'abc/def/ghi/' In [2]: s.strip('/') Out[2]: 'abc/def/ghi' I am referring to the funct ...

Encountering challenges with reusing modules in Angular2

I am currently working on an angular2 website with a root module and a sub level module. However, I have noticed that whatever modules I include in the root module must also be re-included in the sub level module, making them not truly reusable. This is w ...

Experimenting with throws using Jest

One of the functions I'm testing is shown below: export const createContext = async (context: any) => { const authContext = await AuthGQL(context) console.log(authContext) if(authContext.isAuth === false) throw 'UNAUTHORIZED' retu ...

When working with Typescript, you can declare an interface and split its definition across multiple files

I've been developing a software application that utilizes WebSocket with the NodeJS ws package. My networking structure revolves around a module responsible for handling message reception and transmission. Given that I'm working with TypeScript, ...

Combine an array of arrays with its elements reversed within the same array

I am working with an array of numbers that is structured like this: const arrayOfArrays: number[][] = [[1, 2], [1, 3]]; The desired outcome is to have [[1, 2], [2, 1], [1, 3], [3, 1]]. I found a solution using the following approach: // initialize an e ...

Using RxJS switchMap in combination with toArray allows for seamless transformation

I'm encountering an issue with rxjs. I have a function that is supposed to: Take a list of group IDs, such as: of(['1', '2']) Fetch the list of chats for each ID Return a merged list of chats However, when it reaches the toArray ...

Implementing Service Communication

I created an Angular Application using the Visual Studio Template. The structure of the application is as follows: /Clientapp ./app/app.module.shared.ts ./app/app.module.client.ts ./app/app.module.server.ts ./components/* ./services/person-data.service. ...

Guide to importing a function from a Javascript module without declaration

Currently, I am utilizing a third-party Javascript library that includes Typescript type declarations in the form of .d.ts files. Unfortunately, as is often the case, these type declarations are inaccurate. Specifically, they lack a crucial function which ...

Accessing instance variables from a chained observable function in Angular 2/Typescript

Currently, I am utilizing Angular2 along with Typescript. Let's assume that there is a dummy login component and an authentication service responsible for token authentication. In one of the map functions, I intend to set the variable authenticated as ...

The VSCode Extension fails to launch after being packaged

I'm currently working on a straightforward VSCode extension that scans the active open file for any comments containing "//TODO: " and then presents a list of all TODO comments in a webview sidebar tab. While I have a functional prototype that runs s ...

Error encountered: Uncaught SyntaxError - An unexpected token '<' was found while matching all routes within the Next.js middleware

I am implementing a code in the middleware.ts file to redirect users to specific pages based on their role. Here is the code snippet: import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server' import { get ...

Assigning a custom class to the cdk-overlay-pane within a mat-select component is restricted to Angular Material version 10.2.7

I attempted the code below, but it only works for angular material 11. My requirement is to use only angular material 10. providers: [ { provide: MAT_SELECT_CONFIG, useValue: { overlayPanelClass: 'customClass' } } ] There a ...