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: https://i.sstatic.net/PgQHP.png

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

A guide on instantly updating displayed flat/section list elements in React Native

I am in the process of creating a screen called ContactListScreen. The direct child of ContactListScreen is ContactItems, which is a sectionList responsible for rendering each individual ContactItem. However, I have encountered a problem where my ContactIt ...

What is the process for generating an index.d.ts file within a yarn package?

I'm facing an issue with creating the index.d.ts file for my yarn package. Here is my configuration in tsconfig.json: { "include": ["src/**/*"], "exclude": ["node_modules", "**/*.spec.ts"], " ...

Enhancing Skylinkjs functionality using Typescript

Hello fellow developers, I am new to using typescript and currently experimenting with incorporating SkylinkJS into my project. Can anyone guide me on the best practices for utilizing an npm library with TypeScript? If you are interested in checking out t ...

What is the correct way to write an asynchronous Express middleware function in Typescript?

Can anyone help me figure out how to correctly define a return value for an express middleware that utilizes async/await? I've been experimenting with different approaches but haven't found success yet. Additionally, I'm attempting to exten ...

Retrieve the service variable in the routing file

How do I access the service variable in my routing file? I created a UserService with a variable named user and I need to use that variable in my routing file. Here is the approach I tried, but it didn't work: In the routing file, I attempted: cons ...

A guide to implementing typescript with Next.js getStaticProps

I have Next.js set up with the TypeScript feature enabled Currently, I am attempting to utilize the getStaticProps function following the guidelines outlined here: https://nextjs.org/docs/basic-features/typescript Utilizing the GetStaticProps type export ...

Enhancing Vue prop with TypeScript typing

In my Vue component, I am working with a prop called tabs. The format for this prop is expected to be as follows: [{ id: string title: string color: `#${string}` },{ id: string title: string color: `#${string}` }] Currently, I am utilizing Lar ...

Adding a condition to the react-router v6 element: A step-by-step guide

I am currently in the process of updating my project from v5 to v6 of react-router-dom. However, I have encountered an issue. Everything was working fine in v5 <Route path={`${url}/phases/:phaseIndex`}> {(chosenPhase?.type === PhaseTy ...

What are some ways to resolve this console error: "TS2307: Could not locate module '@components/common/ButtonBlock' or its corresponding type declarations."

While the project is running smoothly, I am noticing a multitude of errors appearing in the console of VS Code. How can I eliminate these error messages? It seems to be related to TypeScript. Additionally, I am encountering an error in the browser as well ...

Attempting to compile TypeScript by referencing ng2-bootstrap using Gulp within Visual Studio

I've been struggling with this issue for a few days now, and I'm really hoping someone can help me out. Currently, I am experimenting with Angular2 in an aspnet core project. The setup involves using a gulpfile.js to build .ts files and transfer ...

Encountered a runtime error in NgRx 7.4.0: "Uncaught TypeError: ctor is not a

I'm facing difficulties trying to figure out why I can't register my effects with NgRx version 7.4.0. Despite simplifying my effects class in search of a solution, I keep encountering the following error: main.79a79285b0ad5f8b4e8a.js:33529 Uncau ...

How to access class type arguments within a static method in Typescript: A clever solution

An issue has arisen due to the code below "Static members cannot reference class type parameters." This problem originates from the following snippet of code abstract class Resource<T> { /* static methods */ public static list: T[] = []; ...

Executing a function when a user chooses to exit a webpage using the @HostListener('window:beforeunload') method

Utilizing @HostListener('window:beforeunload') allows me to detect when a user navigates away from the page, prompting a dialog window to open. I wish for an event to be triggered or a method to be executed if the user chooses to leave the page. ...

Errors in TypeScript are being brought up by using if-else statements inside a loop

I am currently working on a function to retrieve referral codes from users. The user inputs a code, which is then checked against the database to see if it exists or not If the code provided matches the current user's code, it should not be accept ...

Zod implements asynchronous validation for minimum, maximum, and length constraints

When working with Zod, setting values can be done as shown below: z.string().max(5); z.string().min(5); z.string().length(5); However, in my scenario, the values (e.g., 5) are not predetermined. They are fetched from an API dynamically. How can I create t ...

Having trouble resolving all parameters for AuthService in Angular

Launching my angular app has hit a roadblock with this perplexing error. Despite attempts to troubleshoot by removing the auth service provider and constructor reference from my component, the issue persists. As a novice in angular, I'm struggling to ...

How can I implement the MUI Drawer component in a separate .jsx file while utilizing BrowserRouter?

If you're interested, I have set up a test case on Github related to my query: https://i.stack.imgur.com/6lHTd.gif Within my App.jsx, the following code is present: <NavDrawer /> <BrowserRouter> <Routes> <Route path=" ...

Creating TypeScript declaration file for exporting JavaScript function

I'm a beginner with TypeScript and I want to learn how to create a declaration file for a custom JavaScript function. I attempted to do this, however, I encountered an error stating "Could not find a declaration file for module './main'." Ad ...

Tips for effectively handling the auth middleware in react.js by utilizing LocalStorage

After making a call to the authentication API, the plan is to save the authentication token in LocalStorage and then redirect to a dashboard that requires token validation for entry. However, an issue arises where the authentication token isn't immedi ...

Navigate to a new tab using this.router.navigate

Is there a way to redirect the user to a specific page with ${id} opening in a new tab, after clicking a button in an angular material dialog box? I want to leave the dialog box open while querying the new page. Currently, the redirect happens but not in a ...