React utilities and ESLint rules for consistent React style
npm install ti-reactuCustom ESLint plugin to enforce React coding standards based on your functional React style guide.
This plugin helps maintain consistent React code style, especially when working with AI code generation. It encodes React best practices into automated rules that catch common issues like class components, function declarations, and inconsistent patterns.
Enforces concise arrow function syntax for single-expression functions. Use expression body (no braces, implicit return) when a function only returns a single expression.
Bad:
``tsx
export let UserCard = ({ name }: { name: string }) => {
return {name}
}
let double = (x: number) => {
return x * 2
}
`
Good:
`tsx
export let UserCard = ({ name }: { name: string }) => (
{name}
)
let double = (x: number) => x * 2
`
This rule is auto-fixable with --fix. JSX expressions are automatically wrapped in parentheses.
Enforces using let instead of const for variable declarations, except for top-level UPPER_CASE constants.
Bad:
`tsx`
const userId = getUserId()
const [count, setCount] = useState(0)
Good:
`tsx
let userId = getUserId()
let [count, setCount] = useState(0)
// Top-level constants are allowed
const API_TIMEOUT = 5000
const MAX_RETRIES = 3
`
This rule is auto-fixable with --fix.
Enforces arrow function syntax instead of function declarations.
Bad:
`tsx
function UserProfile({ userId }: Props) {
return {userId}
}
export function fetchData() {
return fetch('/api/data')
}
`
Good:
`tsx
let UserProfile = ({ userId }: Props) => (
{userId}
)
export let fetchData = () => fetch('/api/data')
`
This rule is auto-fixable with --fix.
Disallows default exports. Use named exports for better refactoring support and explicit imports.
Bad:
`tsx`
export default function UserCard() { ... }
export default UserCard
Good:
`tsx`
export let UserCard = () => ...
Enforces using type instead of interface for type definitions.
Bad:
`tsx
interface UserProps {
name: string
age: number
}
interface AdminProps extends UserProps {
role: string
}
`
Good:
`tsx
type UserProps = {
name: string
age: number
}
type AdminProps = UserProps & {
role: string
}
`
This rule is auto-fixable with --fix.
Disallows class components. Use functional components with hooks instead.
Bad:
`tsx
class UserCard extends React.Component
render() {
return {this.props.name}
}
}
class Counter extends Component {
state = { count: 0 }
render() { ... }
}
`
Good:
`tsx
let UserCard = ({ name }: Props) => (
{name}
)
let Counter = () => {
let [count, setCount] = useState(0)
return ...
}
`
Enforces a consistent file layout where all exported items (types, functions, components) come first, followed by private helper functions.
Bad:
`tsx
let privateHelper = (x: number) => x * 2
export type UserProps = { name: string }
let formatName = (name: string) => name.toUpperCase()
export let UserCard = ({ name }: UserProps) => ...
`
Good:
`tsx
// Exports first
export type UserProps = { name: string }
export let UserCard = ({ name }: UserProps) => (
// Private functions after
let privateHelper = (x: number) => x * 2
let formatName = (name: string) => name.toUpperCase()
`
Disallows long inline event handlers in JSX. Extract complex handlers to named functions for better readability and testability.
Default: Maximum 30 characters allowed for inline handlers.
Configuration:
`js`
"react-style/no-inline-handler": ["error", { maxBodyLength: 30 }]
Bad:
`tsx`
Good:
`tsx
let handleClick = () => {
setLoading(true)
fetchData().then(data => {
setData(data)
setLoading(false)
})
}
// Short handlers are allowed
`
Suggests using early returns instead of wrapping entire component body in conditionals.
Bad:
` {user.email}tsx`
let UserProfile = ({ user }: Props) => {
return user ? (
{user.name}
) : null
}
Good:
`tsx
let UserProfile = ({ user }: Props) => {
if (!user) return null
return (
{user.email}
Component Style Examples
$3
`tsx
type StatusBadgeProps = {
status: 'active' | 'inactive'
label: string
}export let StatusBadge = ({ status, label }: StatusBadgeProps) => (
badge badge-${status}}>{label}
)
`$3
`tsx
type CounterProps = {
initialValue?: number
}export let Counter = ({ initialValue = 0 }: CounterProps) => {
let [count, setCount] = useState(initialValue)
let increment = () => setCount(c => c + 1)
let decrement = () => setCount(c => c - 1)
return (
{count}
)
}
`$3
`tsx
type UserCardProps = {
userId: string
}export let UserCard = ({ userId }: UserCardProps) => {
let { data: user, loading, error } = useUser(userId)
if (loading) return
if (error) return
if (!user) return null
return (
{user.name}
{user.email}
)
}
`$3
`tsx
type UseToggleReturn = [boolean, () => void, (value: boolean) => void]export let useToggle = (initialValue = false): UseToggleReturn => {
let [value, setValue] = useState(initialValue)
let toggle = useCallback(() => setValue(v => !v), [])
return [value, toggle, setValue]
}
`Installation
This plugin is located in the ti-reactu repository. To use it in your project:
1. Add to your
package.json:`json
{
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^8.0.0",
"@typescript-eslint/parser": "^8.0.0",
"eslint": "^9.0.0",
"ti-reactu": "file:/path/to/ti-reactu"
}
}
`2. Create
eslint.config.cjs in your project root:`js
const tsParser = require("@typescript-eslint/parser")
const reactStyle = require("ti-reactu/eslint-plugin-react-style")module.exports = [
{
files: ["/.ts", "/.tsx", "/.js", "/.jsx"],
languageOptions: {
parser: tsParser,
ecmaVersion: 2020,
sourceType: "module",
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
},
plugins: {
"react-style": reactStyle,
},
rules: {
...reactStyle.configs.recommended.rules,
},
},
{
ignores: ["/node_modules/", "/dist/", "/build/"],
},
]
`3. Add scripts to your
package.json:`json
{
"scripts": {
"lint": "eslint . --ext .ts,.tsx,.js,.jsx",
"lint:fix": "eslint . --ext .ts,.tsx,.js,.jsx --fix"
}
}
`Key Principles
The rules encode these principles:
- Arrow functions everywhere: Consistent syntax for components, hooks, and handlers
- Expression bodies when possible: Concise components without unnecessary braces
-
let over const: Except for true constants (UPPER_CASE)
- Types over interfaces: Better composition with &` intersection types