TSX
This page curates a list of example ast-grep rules to check and to rewrite TypeScript with JSX syntax.
TSX and TypeScript are different.
TSX differs from TypeScript because it is an extension of the latter that supports JSX elements. They need distinct parsers because of conflicting syntax.
In order to reduce rule duplication, you can use the languageGlobs
option to force ast-grep to use parse .ts
files as TSX.
Unnecessary useState
Type Has Fix
Description
React's useState
is a Hook that lets you add a state variable to your component. The type annotation of useState
's generic type argument, for example useState<number>(123)
, is unnecessary if TypeScript can infer the type of the state variable from the initial value.
We can usually skip annotating if the generic type argument is a single primitive type like number
, string
or boolean
.
Pattern
ast-grep -p 'useState<number>($A)' -r 'useState($A)' -l tsx
ast-grep -p 'useState<string>($A)' -r 'useState($A)'
ast-grep -p 'useState<boolean>($A)' -r 'useState($A)'
Example
function Component() {
const [name, setName] = useState<string>('React')
}
Diff
function Component() {
const [name, setName] = useState<string>('React')
const [name, setName] = useState('React')
}
Contributed by
Avoid &&
short circuit in JSX Has Fix
Description
In React, you can conditionally render JSX using JavaScript syntax like if
statements, &&
, and ? :
operators. However, you should almost never put numbers on the left side of &&
. This is because React will render the number 0
, instead of the JSX element on the right side. A concrete example will be conditionally rendering a list when the list is not empty.
This rule will find and fix any short-circuit rendering in JSX and rewrite it to a ternary operator.
YAML
id: do-what-brooooooklyn-said
language: Tsx
rule:
kind: jsx_expression
has:
pattern: $A && $B
not:
inside:
kind: jsx_attribute
fix: "{$A ? $B : null}"
Example
<div>{ list.length && list.map(i => <p/>) }</div>
Diff
<div>{ list.length && list.map(i => <p/>) }</div>
<div>{ list.length ? list.map(i => <p/>) : null }</div>
Contributed by
Herrington Darkholme, inspired by @Brooooook_lyn
Rewrite MobX Component Style Has Fix
Description
React and MobX are libraries that help us build user interfaces with JavaScript.
React hooks allow us to use state and lifecycle methods in functional components. But we need follow some hook rules, or React may break. MobX has an observer
function that makes a component update when data changes.
When we use the observer
function like this:
export const Example = observer(() => {…})
ESLint, the tool that checks hooks, thinks that Example
is not a React component, but just a regular function. So it does not check the hooks inside it, and we may miss some wrong usages.
To fix this, we need to change our component style to this:
const BaseExample = () => {…}
const Example = observer(BaseExample)
Now ESLint can see that BaseExample
is a React component, and it can check the hooks inside it.
YAML
id: rewrite-mobx-component
language: typescript
rule:
pattern: export const $COMP = observer($FUNC)
fix: |-
const Base$COMP = $FUNC
export const $COMP = observer(Base$COMP)
Example
export const Example = observer(() => {
return <div>Hello World</div>
})
Diff
export const Example = observer(() => {
return <div>Hello World</div>
})
const BaseExample = () => {
return <div>Hello World</div>
}
export const Example = observer(BaseExample)
Contributed by
Avoid Unnecessary React Hook
Description
React hook is a powerful feature in React that allows you to use state and other React features in a functional component.
However, you should avoid using hooks when you don't need them. If the code does not contain using any other React hooks, it can be rewritten to a plain function. This can help to separate your application logic from the React-specific UI logic.
YAML
id: unnecessary-react-hook
language: Tsx
utils:
hook_call:
has:
kind: call_expression
regex: ^use
stopBy: end
rule:
any:
- pattern: function $FUNC($$$) { $$$ }
- pattern: let $FUNC = ($$$) => $$$
- pattern: const $FUNC = ($$$) => $$$
has:
pattern: $BODY
kind: statement_block
stopBy: end
constraints:
FUNC: {regex: ^use }
BODY: { not: { matches: hook_call } }
Example
function useIAmNotHookActually(args) {
console.log('Called in React but I dont need to be a hook')
return args.length
}
const useIAmNotHookToo = (...args) => {
console.log('Called in React but I dont need to be a hook')
return args.length
}
function useTrueHook() {
useEffect(() => {
console.log('Real hook')
})
}
Contributed by
Reverse React Compiler™ Has Fix
Description
React Compiler is a build-time only tool that automatically optimizes your React app, working with plain JavaScript and understanding the Rules of React without requiring a rewrite. It optimizes apps by automatically memoizing code, similar to useMemo
, useCallback
, and React.memo
, reducing unnecessary recomputation due to incorrect or forgotten memoization.
Reverse React Compiler™ is a parody tweet that works in the opposite direction. It takes React code and removes memoization, guaranteed to make your code slower. (not necessarily)
It is originally written in Babel and this is an ast-grep version of it.
The Original Babel Implementation
For comparison purposes only. Note the original code does not correctly rewrite useMemo
.
const ReverseReactCompiler = ({ types: t }) => ({
visitor: {
CallExpression(path) {
const callee = path.node.callee;
if (
t.isIdentifier(callee, { name: "useMemo" }) ||
t.isIdentifier(callee, { name: "useCallback" }) ||
t.isIdentifier(callee, { name: "memo" })
) {
path.replaceWith(args[0]);
}
},
},
});
YAML
id: rewrite-cache
language: tsx
rule:
any:
- pattern: useCallback($FN, $$$)
- pattern: memo($FN, $$$)
fix: $FN
---
id: rewrite-use-memo
language: tsx
rule: { pattern: 'useMemo($FN, $$$)' }
fix: ($FN)() # need IIFE to wrap memo function
Example
const Component = () => {
const [count, setCount] = useState(0)
const increment = useCallback(() => {
setCount((prevCount) => prevCount + 1)
}, [])
const expensiveCalculation = useMemo(() => {
// mock Expensive calculation
return count * 2
}, [count])
return (
<>
<p>Expensive Result: {expensiveCalculation}</p>
<button onClick={increment}>{count}</button>
</>
)
}
Diff
const Component = () => {
const [count, setCount] = useState(0)
const increment = useCallback(() => {
setCount((prevCount) => prevCount + 1)
}, [])
const increment = () => {
setCount((prevCount) => prevCount + 1)
}
const expensiveCalculation = useMemo(() => {
// mock Expensive calculation
return count * 2
}, [count])
const expensiveCalculation = (() => {
// mock Expensive calculation
return count * 2
})()
return (
<>
<p>Expensive Result: {expensiveCalculation}</p>
<button onClick={increment}>{count}</button>
</>
)
}
Contributed by
Inspired by Aiden Bai