Tal Rofe
Tal Rofe

Reputation: 1874

React cannot augment React types configuration with separate file

There is some type in react library I try to overload with an augmentation:

In my project, I created a file: ./@types/react/index.d.ts, with content:

import type React from 'react';

declare module 'react' {
    function memo<A, B>(Component: (props: A) => B): (props: A) => React.ReactElement | null;
}

I did to solve some issue I had with TypeScript, which is irrelevant for this question.

I have also provided this @types folder in my ./tsconfig.json file:

"typeRoots": ["./node_modules/@types", "./@types"],

But issue still shows (i.e. my overload does not affect the initial issue).

If instead, in the file I had the issue, I just paste the code block:

declare module 'react' {
    function memo<A, B>(Component: (props: A) => B): (props: A) => React.ReactElement | null;
}

...then issue is resolved.

But I rather not use this way, inline-like. I don't want this declaration to show in the file.

Could anyone tell why the @types folder solution does not work?


Reproduce: Create a dummy project with boilerplate of CRA:

npx create-react-app whatever --template typescript

Modify ./tsconfig.json file with content:

{
  "compilerOptions": {
    "target": "es5",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noFallthroughCasesInSwitch": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx",
    "typeRoots": [
      "./node_modules/@types",
      "./@types"
    ]  
  },
  "include": [
    "src"
  ]
}

Create ./@types/react/index.d.ts file with content:

declare module "react" { // augment React types
    function memo<A, B>(Component: (props: A) => B): (props: A) => React.ReactElement | null
    // return type is same as ReturnType<ExoticComponent<any>>
  }

Create ./src/Filter.tsx file:

import React from 'react';

import type { IOption } from './option';

import FilterView from './Filter.view';

interface IProps<T> {
    readonly title: string;
    readonly options: IOption<T>[];
    readonly selectedOption: T;
    readonly onSelectOption: (value: T) => void;
}

const Filter = <T,>(props: React.PropsWithChildren<IProps<T>>) => (
    <FilterView
        title={props.title}
        options={props.options}
        selectedOption={props.selectedOption}
        onSelectOption={props.onSelectOption}
    />
);

Filter.displayName = 'Filter';
Filter.defaultProps = {};

export default React.memo(Filter);

Create Filter.view.tsx file:

import React from 'react';

import type { IOption } from './option';

import classes from './Filter.module.scss';

interface IProps<T> {
    readonly title: string;
    readonly options: IOption<T>[];
    readonly selectedOption: T;
    readonly onSelectOption: (value: T) => void;
}

const FilterView = <T,>(props: React.PropsWithChildren<IProps<T>>) => {
    return (
        <div className={classes['container']}>
            <h5 className={classes['container__title']}>{props.title}</h5>

            {props.options.map((option, index) => (
                <button
                    key={index}
                    className={classes['blabla']}
                    type="button"
                    onClick={() => props.onSelectOption(option.value)}
                >
                    {option.label}
                </button>
            ))}
        </div>
    );
};

FilterView.displayName = 'FilterView';
FilterView.defaultProps = {};

export default React.memo(FilterView);

Create ./option.ts file:

export interface IOption<T> {
    readonly value: T;
    readonly label: string;
}

Then you'll see an error in Filter.tsx file.

Upvotes: 2

Views: 354

Answers (1)

ghybs
ghybs

Reputation: 53370

If your module augmentation is in src/@types/react/index.d.ts, you should fill your typeRoots tsconfig option with "./src/@types" (instead of "./@types"):

"typeRoots": [
    "./node_modules/@types",
    "./src/@types"
]

All paths are relative to the tsconfig.json.

Note: there is a typo in the import:

import React from 'react'; // React first letter uppercase to match how it is used in the file: React.ReactElement

Demo: https://codesandbox.io/s/hopeful-silence-qzu12v?file=/tsconfig.json


Once you move your global type files outside your ./src directory, they are no longer automatically included in your TS compilation, since the tsconfig mentions only that folder:

  "include": [
    "src"
  ]

That is why you then have to include the new folder in typeRoots option list.

However, TS finds the normal react types in "./node_modules/@types/react" first, and stops there. Our augmentation file is left behind...

So just make sure to specify your own type root first:

"typeRoots": [
    "./@types", // Own root with augmentations first
    "./node_modules/@types" // base root last
]

That way, TS picks up our augmentation file first in "./@types/react". Within that file, it finds the import "react" again, and looks for the corresponding "base" react type definition file.

Demo: https://codesandbox.io/s/wizardly-shaw-dw84bj?file=/tsconfig.json:519-566

Upvotes: 2

Related Questions