Collection of transforms for jscodeshift related to @types/react
.
The codemod helps to fix potential TypeScript compile errors when upgrading to @types/react@^18.0.0
.
However, we recommend to apply this codemod if you're using @types/react@^17.0.30
.
$ npx types-react-codemod preset-18 ./src
? Pick transforms to apply (Press <space> to select, <a> to toggle all, <i> to invert selection, and <enter> to proce
ed)
❯◯ context-any
◉ deprecated-react-type
◉ deprecated-sfc-element
◉ deprecated-sfc
◉ deprecated-stateless-component
◯ implicit-children
◯ useCallback-implicit-any
All done.
Results:
0 errors
20 unmodified
0 skipped
3 ok
Time elapsed: 0.229seconds
$ npx types-react-codemod <codemod> <paths...>
Positionals:
codemod [string] [required] [choices: "context-any", "deprecated-legacy-ref",
"deprecated-prop-types-types", "deprecated-react-child",
"deprecated-react-fragment", "deprecated-react-node-array",
"deprecated-react-text", "deprecated-react-type", "deprecated-sfc-element",
"deprecated-sfc", "deprecated-stateless-component",
"deprecated-void-function-component", "implicit-children", "preset-18",
"preset-19", "refobject-defaults", "scoped-jsx", "useCallback-implicit-any",
"useRef-required-initial"]
paths [string] [required]
Options:
--version Show version number [boolean]
--help Show help [boolean]
--dry [boolean] [default: false]
--ignore-pattern [string] [default: "**/node_modules/**"]
--verbose [boolean] [default: false]
Examples:
types-react-codemod preset-18 ./ Ignores `node_modules` and `build`
--ignore-pattern folders
"**/{node_modules,build}/**"
Some transforms change code they shouldn't actually change. Fixing all of these requires a lot of implementation effort. When considering false-positives vs false-negatives, codemods opt for false-positives. The reason being that a false-positive can be reverted easily (assuming you have the changed code in Version Control e.g. git) while a false-negative requires manual input.
preset-18
deprecated-react-type
deprecated-sfc-element
deprecated-sfc
deprecated-stateless-component
context-any
implicit-children
useCallback-implicit-any
preset-19
deprecated-prop-types-types
deprecated-legacy-ref
deprecated-react-child
deprecated-react-text
deprecated-void-function-component
refobject-defaults
scoped-jsx
useRef-required-initial
This codemod combines all codemods for React 18 types.
You can interactively pick the codemods included.
By default, the codemods that are definitely required to upgrade to @types/react@^18.0.0
are selected.
The other codemods may or may not be required.
You should select all and audit the changed files regardless.
class Component extends React.Component<Props> {
+ context: any
render() {
return this.context.someContextProperty;
}
}
You should only apply this codemod to files where the type-checker complains about access of unknown
in this.context
.
We'll check for any occurence of context
(case-sensitive) in a React.Component
body (or React.PureComponent
).
If we find any occurence of context
we'll add context: any
declaration to the class body.
We'll add context: any
even if you write const { context } = props
.
This simplifies the implementation tremendously and follows the overall rationale for false-positives: it can be reverted easily and at worst restores the behavior of React 17 typings.
Class inheritance chains are not handled.
class A extends React.Component {}
class B extends A {
render() {
// will error since the transform does not add `context: any` to the declaration of `A` nor `B`.
// It's up to you to decide whether `A` or `B` should have this declaration
return this.context.value;
}
}
We'll also miss usage of context
if it's accessed outside of the class body e.g.
function getValue(that) {
return that.context.value;
}
class A extends React.Component {
render() {
return getValue(this);
}
}
This doesn't really follow the general transform rationale of "over-applying" since at worst we restore React 17 behavior.
I just think that most class components do not use this.context
(or already have a type declaration) somewhere else.
-React.ReactType
+React.ElementType
-React.SFC
+React.FC
-React.StatelessComponent
+React.FunctionComponent
-React.SFCElement
+React.FunctionComponentElement
They simply rename identifiers with a specific name.
If you have a type with the same name from a different package, then the rename results in a false positive.
For example, ink
also has a StatelessComponent
but you don't need to rename that type since it's not deprecated.
-React.FunctionComponent<Props>
+React.FunctionComponent<React.PropsWithChildren<Props>>
-React.FunctionComponent
+React.FunctionComponent<React.PropsWithChildren<unknown>>
This transform will wrap the props type of React.FunctionComponent
(and FC
, ComponentType
, SFC
and StatelessComponent
) with React.PropsWithChildren
.
Note, that the transform assumes React.PropsWithChildren
is available.
We can't add that import since React.PropsWithChildren
can be available via tsconfig.json
.
We'll apply React.PropsWithChildren
everytime.
If you have a component that doesn't actually take children
, we'll not fix what removal of implicit children should've fixed.
Similarly, if your props already have children
declared, PropsWithChildren
will be redundant.
Redundant PropsWithChildren
are only problematic stylistically.
MyFunctionComponent<Props>
where MyFunctionComponent
comes from import { FunctionComponent as MyFunctionComponent } from 'react'
will be ignored.
In other words, the transform will not wrap Props
in React.PropsWithChildren
.
The transform would need to implement scope tracking for this pattern to get fixed.
-React.useCallback((event) => {})
+React.useCallback((event: any) => {})
This transform should only be applied to files where TypeScript errors with "Parameter '*' implicitly has an 'any' type.(7006)" in useCallback
.
If the callback param is inferrable by TypeScript we might apply any
without need.
In the example below the type of event
is inferrable and adding any
essentially reduces type coverage.
This is why it's recommended to only apply useCallback-implicit-any
to files that produce "Parameter '*' implicitly has an 'any' type.(7006)" when type-checking with @types/react@^18.0.0
.
type CreateCallback = () => (event: Event) => void;
-const createCallback: CreateCallback = () => useCallback((event) => {}, [])
+const createCallback: CreateCallback = () => useCallback((event: any) => {}, [])
This codemod combines all codemods for React 19 types.
You can interactively pick the codemods included.
By default, the codemods that are definitely required to upgrade to @types/react@^19.0.0
are selected.
The other codemods may or may not be required.
You should select all and audit the changed files regardless.
+import * as PropTypes from "prop-types";
import * as React from "react";
-declare const requireable: React.Requireable<React.ReactNode>;
+declare const requireable: PropTypes.Requireable<React.ReactNode>;
-declare const validator: React.Validator<React.ReactNode>;
+declare const requireable: PropTypes.Validator<React.ReactNode>;
-declare const validationMap: React.ValidationMap<{}>;
+declare const requireable: PropTypes.ValidationMap<React.ReactNode>;
-declare const weakValidationMap: React.WeakValidationMap<{}>;
+declare const requireable: PropTypes.WeakValidationMap<React.ReactNode>;
import * as React from "react";
interface Props {
- ref?: React.LegacyRef;
+ ref?: React.Ref;
}
Importing LegacyRef
via aliased named import will result in the transform being skipped.
import { LegacyRef as MyLegacyRef } from "react";
interface Props {
// not transformed
ref?: MyLegacyRef;
}
import * as React from "react";
interface Props {
- label?: React.ReactChild;
+ label?: React.ReactElement | number | string;
}
Importing ReactChild
via aliased named import will result in the transform being skipped.
import { ReactChild as MyReactChild } from "react";
interface Props {
// not transformed
label?: MyReactChild;
}
import * as React from "react";
interface Props {
- children?: React.ReactNodeArray;
+ children?: ReadonlyArray<React.ReactNode>;
}
Importing ReactNodeArray
via aliased named import will result in the transform being skipped.
import { ReactNodeArray as MyReactNodeArray } from "react";
interface Props {
// not transformed
children?: MyReactNodeArray;
}
import * as React from "react";
interface Props {
- children?: React.ReactFragment;
+ children?: Iterable<React.ReactNode>;
}
Importing ReactFragment
via aliased named import will result in the transform being skipped.
import { ReactFragment as MyReactFragment } from "react";
interface Props {
// not transformed
children?: MyReactFragment;
}
import * as React from "react";
interface Props {
- label?: React.ReactText;
+ label?: number | string;
}
Importing ReactText
via aliased named import will result in the transform being skipped.
import { ReactText as MyReactText } from "react";
interface Props {
// not transformed
label?: MyReactText;
}
WARNING: Only apply to codebases using @types/react@^18.0.0
.
In earlier versions of @types/react
this codemod would change the typings.
import * as React from "react";
-const Component: React.VFC = () => {}
+const Component: React.FC = () => {}
-const Component: React.VoidFunctionComponent = () => {}
+const Component: React.FunctionComponent = () => {}
WARNING: This is an experimental codemod to intended for codebases using unpublished types. Only use if you're using DefinitelyTyped/DefinitelyTyped#64896.
RefObject
no longer makes current
nullable by default
import * as React from "react";
-const myRef: React.RefObject<View>
+const myRef: React.RefObject<View | null>
Importing RefObject
via aliased named import will result in the transform being skipped.
import { RefObject as MyRefObject } from "react";
// not transformed
const myRef: MyRefObject<View>;
Ensures access to global JSX namespace is now scoped to React (see DefinitelyTyped/DefinitelyTyped#64464).
This codemod tries to match the existing import style but isn't perfect.
If the import style doesn't match your preferences, you should set up auto-fixable lint rules to match this e.g. import/order
.
+import { JSX } from 'react'
-const element: JSX.Element = <div />;
+const element: JSX.Element = <div />;
import * as React from 'react';
-const element: JSX.Element = <div />;
+const element: React.JSX.Element = <div />;
useRef
now always requires an initial value.
Implicit undefined
is forbidden.
import * as React from "react";
-React.useRef()
+React.useRef(undefined)
Importing useRef
via aliased named import will result in the transform being skipped.
import { useRef as useReactRef } from "react";
// not transformed
useReactRef<number>();
The following list contains officially supported runtimes. Please file an issue for runtimes that are not included in this list.
- Node.js
16.x || 18.x || 20.x