Skip to content

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

bash
ast-grep -p 'useState<number>($A)' -r 'useState($A)' -l tsx
bash
ast-grep -p 'useState<string>($A)' -r 'useState($A)'
bash
ast-grep -p 'useState<boolean>($A)' -r 'useState($A)'

Example

ts
function Component() {
  const [name, setName] = useState<string>('React')
}

Diff

ts
function Component() {
  const [name, setName] = useState<string>('React') 
  const [name, setName] = useState('React') 
}

Contributed by

Herrington Darkholme

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

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

tsx
<div>{ list.length && list.map(i => <p/>) }</div>

Diff

tsx
<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:

JavaScript
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:

JavaScript
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

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

js
export const Example = observer(() => {
  return <div>Hello World</div>
})

Diff

js
export const Example = observer(() => { 
  return <div>Hello World</div>         
})                                      
const BaseExample = () => {             
  return <div>Hello World</div>         
}                                       
export const Example = observer(BaseExample) 

Contributed by

Bryan Lee

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

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

tsx
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

Herrington Darkholme

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.

js
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

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

tsx
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

tsx
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

Made with ❤️ with Rust