npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

ti-reactu

v1.0.1

Published

React utilities and ESLint rules for consistent React style

Readme

eslint-plugin-react-style

Custom 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.

Rules

react-style/prefer-concise-arrow-function (error, fixable)

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:

export let UserCard = ({ name }: { name: string }) => {
  return <div>{name}</div>
}

let double = (x: number) => {
  return x * 2
}

Good:

export let UserCard = ({ name }: { name: string }) => (
  <div>{name}</div>
)

let double = (x: number) => x * 2

This rule is auto-fixable with --fix. JSX expressions are automatically wrapped in parentheses.

react-style/no-const-variables (error, fixable)

Enforces using let instead of const for variable declarations, except for top-level UPPER_CASE constants.

Bad:

const userId = getUserId()
const [count, setCount] = useState(0)

Good:

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.

react-style/no-function-declaration (error, fixable)

Enforces arrow function syntax instead of function declarations.

Bad:

function UserProfile({ userId }: Props) {
  return <div>{userId}</div>
}

export function fetchData() {
  return fetch('/api/data')
}

Good:

let UserProfile = ({ userId }: Props) => (
  <div>{userId}</div>
)

export let fetchData = () => fetch('/api/data')

This rule is auto-fixable with --fix.

react-style/no-default-export (error)

Disallows default exports. Use named exports for better refactoring support and explicit imports.

Bad:

export default function UserCard() { ... }
export default UserCard

Good:

export let UserCard = () => ...

react-style/no-interface (error, fixable)

Enforces using type instead of interface for type definitions.

Bad:

interface UserProps {
  name: string
  age: number
}

interface AdminProps extends UserProps {
  role: string
}

Good:

type UserProps = {
  name: string
  age: number
}

type AdminProps = UserProps & {
  role: string
}

This rule is auto-fixable with --fix.

react-style/no-class-component (error)

Disallows class components. Use functional components with hooks instead.

Bad:

class UserCard extends React.Component<Props> {
  render() {
    return <div>{this.props.name}</div>
  }
}

class Counter extends Component {
  state = { count: 0 }
  render() { ... }
}

Good:

let UserCard = ({ name }: Props) => (
  <div>{name}</div>
)

let Counter = () => {
  let [count, setCount] = useState(0)
  return ...
}

react-style/enforce-file-layout (error)

Enforces a consistent file layout where all exported items (types, functions, components) come first, followed by private helper functions.

Bad:

let privateHelper = (x: number) => x * 2

export type UserProps = { name: string }

let formatName = (name: string) => name.toUpperCase()

export let UserCard = ({ name }: UserProps) => ...

Good:

// Exports first
export type UserProps = { name: string }

export let UserCard = ({ name }: UserProps) => (
  <div>{formatName(name)}</div>
)

// Private functions after
let privateHelper = (x: number) => x * 2
let formatName = (name: string) => name.toUpperCase()

react-style/no-inline-handler (error)

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:

"react-style/no-inline-handler": ["error", { maxBodyLength: 30 }]

Bad:

<button onClick={() => {
  setLoading(true)
  fetchData().then(data => {
    setData(data)
    setLoading(false)
  })
}}>
  Submit
</button>

Good:

let handleClick = () => {
  setLoading(true)
  fetchData().then(data => {
    setData(data)
    setLoading(false)
  })
}

<button onClick={handleClick}>Submit</button>

// Short handlers are allowed
<button onClick={() => setCount(c => c + 1)}>+</button>

react-style/prefer-early-return (warn)

Suggests using early returns instead of wrapping entire component body in conditionals.

Bad:

let UserProfile = ({ user }: Props) => {
  return user ? (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  ) : null
}

Good:

let UserProfile = ({ user }: Props) => {
  if (!user) return null

  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  )
}

Component Style Examples

Simple Component (expression body)

type StatusBadgeProps = {
  status: 'active' | 'inactive'
  label: string
}

export let StatusBadge = ({ status, label }: StatusBadgeProps) => (
  <span className={`badge badge-${status}`}>{label}</span>
)

Component with State (block body)

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 (
    <div>
      <button onClick={decrement}>-</button>
      <span>{count}</span>
      <button onClick={increment}>+</button>
    </div>
  )
}

Component with Early Returns

type UserCardProps = {
  userId: string
}

export let UserCard = ({ userId }: UserCardProps) => {
  let { data: user, loading, error } = useUser(userId)

  if (loading) return <Spinner />
  if (error) return <ErrorMessage error={error} />
  if (!user) return null

  return (
    <div>
      <h2>{user.name}</h2>
      <p>{user.email}</p>
    </div>
  )
}

Custom Hook

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:
{
  "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"
  }
}
  1. Create eslint.config.cjs in your project root:
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/**"],
  },
]
  1. Add scripts to your package.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
  • Named exports only: Explicit imports, better refactoring
  • Functional components only: Hooks provide all the functionality you need
  • Exports first: Public API at the top of each file
  • Extract handlers: Named functions for complex event handlers
  • Early returns: Reduce nesting in conditional rendering