UNPKG

types-react-codemod

Version:

Collection of transforms for [jscodeshift](https://github.com/facebook/jscodeshift) related to `@types/react`.

264 lines (204 loc) 8.88 kB
# types-react-codemod Collection of transforms for [jscodeshift](https://github.com/facebook/jscodeshift) related to `@types/react`. ## Getting started 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`. ```bash $ 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 ``` ## Usage ```bash $ npx types-react-codemod --help types-react-codemod <codemod> <paths...> Positionals: codemod [string] [required] [choices: "context-any", "deprecated-react-child", "deprecated-react-text", "deprecated-react-type", "deprecated-sfc-element", "deprecated-sfc", "deprecated-stateless-component", "implicit-children", "preset-18", "preset-19", "useCallback-implicit-any"] 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}/**" ``` ## Available transforms 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-react-text` ### `preset-18` 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. ### `context-any` ```diff 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. #### false-positive on `context` usage 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. #### false-negative when inheriting from another component Class inheritance chains are not handled. ```tsx 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. ```tsx 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. ### All `deprecated-` transforms ```diff -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. ### `implicit-children` ```diff -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`. #### `implicit-children` false-positive pattern A 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. #### `implicit-children` false-positive pattern B `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. ### `useCallback-implicit-any` ```diff -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`. #### `useCallback-implicit-any` false-positive pattern A 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`. ```diff type CreateCallback = () => (event: Event) => void; -const createCallback: CreateCallback = () => useCallback((event) => {}, []) +const createCallback: CreateCallback = () => useCallback((event: any) => {}, []) ``` ### `preset-19` 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. ### `deprecated-react-child` ```diff import * as React from "react"; interface Props { - label?: React.ReactChild; + label?: React.ReactElement | number | string; } ``` #### `deprecated-react-text` false-negative pattern A Importing `ReactChild` via aliased named import will result in the transform being skipped. ```tsx import { ReactChild as MyReactChild } from "react"; interface Props { // not transformed label?: MyReactChild; } ``` ### `deprecated-react-text` ```diff import * as React from "react"; interface Props { - label?: React.ReactText; + label?: number | string; } ``` #### `deprecated-react-text` false-negative pattern A Importing `ReactText` via aliased named import will result in the transform being skipped. ```tsx import { ReactText as MyReactText } from "react"; interface Props { // not transformed label?: MyReactText; } ``` ### `deprecated-void-function-component` WARNING: Only apply to codebases using `@types/react@^18.0.0`. In earlier versions of `@types/react` this codemod would change the typings. ```diff import * as React from "react"; -const Component: React.VFC = () => {} +const Component: React.FC = () => {} -const Component: React.VoidFunctionComponent = () => {} +const Component: React.FunctionComponent = () => {} ```