Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typescript declaration merging to expand or override module type or interface property

I'm using the types from @types/react-router-dom version 4.3.1 which imports types from @types/react-router and @types/history.

In my application I always have two properties in the location state and I want them to automatically be pulled from the RouteComponentProps interface, without having to pass a custom LocationState type variable to the interface every time.

 

The definition of the RouteComponentProps interface is in react-router and it goes as follows:

import * as H from 'history';

export interface RouteComponentProps<Params extends { [K in keyof Params]?: string } = {}, C extends StaticContext = StaticContext, S = H.LocationState> {
  history: H.History;
  location: H.Location<S>;
  match: match<Params>;
  staticContext?: C;
}

The definitions of the referenced interfaces/types from history are:

export interface Location<S = LocationState> {
    pathname: Pathname;
    search: Search;
    state: S;
    hash: Hash;
    key?: LocationKey;
}

export type LocationState = History.LocationState;

export namespace History {
    export type LocationState = any;
}

 

What I want is to expand the Location interface's state property type definition to include a custom interface (i.e. include the properties that are always available). So something like state: S & { A: string; B: string; }.

 

I have tried redeclaring the RouteComponentProps interface inside a module declaration for react-router but everything I try results in All declarations of 'RouteComponentProps' must have identical type parameters.ts(2428) or Subsequent property declarations must have the same type. Property 'location' must be of type 'Location<S>', but here has type 'any'.ts(2717).

I have also tried redeclaring the Location interface inside the history module declaration with the same result. After which I've tried redeclaring the LocationState type both inside and outside the Historynamespace but that always results in Duplicate identifier 'LocationState'.ts(2300).

 

Is there anything I can do to get the desired behaviour other than writing a custom interface for RouteComponentProps with a different name? I want to be able to import that interface in the project, extend a component's props with it and have properties' A and B types accessible from props.location.state but also to be able to treat any other properties as any, without passing a type variable definition every time.

like image 904
5ar Avatar asked Sep 11 '25 11:09

5ar


1 Answers

So basically, what you are trying to achieve is called module augmentation. Since it is not possible to change the type of an existing property (for obvious reason, it would break everything that already depends on that), the only way is to augment the Location with a new property that would hold your custom properties.

// global.d.ts

import { State, Key, Path } from "history"

declare module 'history' {
  export interface Location<S extends State = State> extends Path {
    state: S;
    customState: { A: string; B: string; }
    key: Key;
  }
}

So, where you are adding those properties, if you want the Location to be properly typed, you have no choice than using a custom property.

The other solution you know it, it is to create a new interface that you will use everywhere.

With the module augmentation at least, even if you need a new property, you do not have to re-type everything everywhere.

Hope that this little explanation will help you out.

Cheers,

like image 169
Adrien De Peretti Avatar answered Sep 14 '25 05:09

Adrien De Peretti