skip to Main Content

I am using Typescript in a React project, and I have this code that I got with the help of Chatgpt:

import React from "react";
import { Route, Navigate, RouteProps } from "react-router-dom";
import { useAuth } from "./AuthContext";

interface PrivateRouteProps extends RouteProps {
  children?: React.ReactNode;
}

const PrivateRoute: React.FC<PrivateRouteProps> = ({ children, ...rest }) => {
  const { token } = useAuth();

  return (
    <Route
      {...rest}
      element={token ? children : <Navigate to="/login" replace />}
    />
  );
};

export default PrivateRoute;

It is showing this error:

An interface can only extend an object type or intersection of object
types with statically known members.

The error is coming from "extends RouteProps" in this line:

interface PrivateRouteProps extends RouteProps {
  children?: React.ReactNode;
}

2

Answers


  1. This is happening because RouteProps isn’t an interface, it’s a union of multiple interfaces. From the react-router-dom 6.3.0 type definitions:

    export declare type RouteProps = PathRouteProps | LayoutRouteProps | IndexRouteProps
    

    You can’t extend an interface from a union, but you can do essentially the same thing with a type intersection:

    type PrivateRouteProps = RouteProps & {
      children?: React.ReactNode
    }
    

    There are some very slight differences between extending an interface and doing an intersection, but they rarely matter. If you’d like to know more see: https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#differences-between-type-aliases-and-interfaces

    Login or Signup to reply.
  2. The basic issue is that you can’t extend a type declaration, you can only extend interfaces. Nicholas’s answer addresses this sufficiently and there is not more I could add.

    Source

    export interface PathRouteProps {
      caseSensitive?: NonIndexRouteObject["caseSensitive"];
      path?: NonIndexRouteObject["path"];
      id?: NonIndexRouteObject["id"];
      lazy?: LazyRouteFunction<NonIndexRouteObject>;
      loader?: NonIndexRouteObject["loader"];
      action?: NonIndexRouteObject["action"];
      hasErrorBoundary?: NonIndexRouteObject["hasErrorBoundary"];
      shouldRevalidate?: NonIndexRouteObject["shouldRevalidate"];
      handle?: NonIndexRouteObject["handle"];
      index?: false;
      children?: React.ReactNode;
      element?: React.ReactNode | null;
      hydrateFallbackElement?: React.ReactNode | null;
      errorElement?: React.ReactNode | null;
      Component?: React.ComponentType | null;
      HydrateFallback?: React.ComponentType | null;
      ErrorBoundary?: React.ComponentType | null;
    }
    
    export interface LayoutRouteProps extends PathRouteProps {}
    
    export interface IndexRouteProps {
      caseSensitive?: IndexRouteObject["caseSensitive"];
      path?: IndexRouteObject["path"];
      id?: IndexRouteObject["id"];
      lazy?: LazyRouteFunction<IndexRouteObject>;
      loader?: IndexRouteObject["loader"];
      action?: IndexRouteObject["action"];
      hasErrorBoundary?: IndexRouteObject["hasErrorBoundary"];
      shouldRevalidate?: IndexRouteObject["shouldRevalidate"];
      handle?: IndexRouteObject["handle"];
      index: true;
      children?: undefined;
      element?: React.ReactNode | null;
      hydrateFallbackElement?: React.ReactNode | null;
      errorElement?: React.ReactNode | null;
      Component?: React.ComponentType | null;
      HydrateFallback?: React.ComponentType | null;
      ErrorBoundary?: React.ComponentType | null;
    }
    
    export type RouteProps = PathRouteProps | LayoutRouteProps | IndexRouteProps;
    

    You can "extend" the union. Something along the following:

    type PrivateRouteProps = RouteProps & {
      children?: React.ReactNode;
    }
    

    But

    But after you fix the Typescript issue you are going to run into React-Router-DOM issues trying to use this PrivateRoute component.

    • Route component children can only be another Route component.
    • The Route component can’t be rendered directly, it can only be rendered as a child of the Routes component, or the Route component in the case of nested routes.

    Instead of rendering a children prop, an Outlet component should be rendered for the nested routes to render out their content. This also really simplifies the implementation.

    Example:

    import React from "react";
    import { Route, Navigate } from "react-router-dom";
    import { useAuth } from "./AuthContext";
    
    const PrivateRoute = () => {
      const { token } = useAuth();
    
      return token ? <Outlet /> : <Navigate to="/login" replace />;
    };
    
    export default PrivateRoute;
    

    Usage:

    <BrowserRouter>
      <Routes>
        <Route path="/" element={<PrivateRoutes />} >
          {/* Protected routes */}
          <Route path="dashboard" element={<Dashboard />} />
          <Route path="about" element={<About />} />
        </Route>
    
        {/* Unprotected routes */}
        <Route path="/login" element={<Login />} />
        <Route path="*" element={<PageNotFound />} />
      </Routes>
    </BrowserRouter>
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search