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

@boring-stack-pkg/eslint-plugin-react-component-architecture

v0.3.0

Published

ESLint plugin enforcing React component architecture conventions.

Readme

eslint-plugin-react-component-architecture

ESLint plugin enforcing React component architecture conventions from AGENTS.md.

Why

React components benefit from enforcing a strict folder structure and separation of concerns. This plugin ensures that:

  • Components are properly structured with hooks, types, tests, and stories as siblings
  • State management is isolated in custom hooks, not in component bodies
  • JSX templates stay minimal and clean (no inline functions or complex logic)
  • Props describe visual state only, not business logic
  • TypeScript interfaces follow naming conventions
  • Imports use consistent patterns (e.g., named imports for React)
  • Dark mode classes are removed (light mode only)

Install

pnpm add -D @boring-stack-pkg/eslint-plugin-react-component-architecture @typescript-eslint/parser

Usage (flat config)

// eslint.config.mjs
import tsParser from "@typescript-eslint/parser";
import reactComponentArchitecture from "@boring-stack-pkg/eslint-plugin-react-component-architecture";

export default [
  {
    files: ["**/*.{ts,tsx}"],
    languageOptions: {
      parser: tsParser,
      parserOptions: { ecmaVersion: "latest", sourceType: "module", ecmaFeatures: { jsx: true } },
    },
    plugins: { "react-component-architecture": reactComponentArchitecture },
    rules: reactComponentArchitecture.configs.recommended.rules,
  },
];

The recommended preset enables all 15 rules. Most rules are set to "error". Heuristic rules (no-state-in-component-body, no-jsx-computation, props-must-be-visual) default to "warn".

Rules

| Rule | Type | Description | |------|------|-------------| | component-folder-structure | Problem | Enforce required sibling files in component folders | | index-must-reexport-default | Problem | index.ts must re-export component default export | | no-state-in-component-body | Suggestion | Move state hooks to custom hooks (.hooks.ts) | | no-inline-jsx-functions | Suggestion | Use function references instead of inline functions for handlers | | no-jsx-computation | Suggestion | Extract complex JSX computations into hooks | | classnames-required | Suggestion | Use classNames for dynamic className values | | classnames-import-name | Suggestion | Import classnames with correct name | | no-dark-mode-classes | Suggestion | Remove dark: Tailwind classes (light mode only) | | interface-prefix-i | Suggestion | Interfaces should be prefixed with 'I' | | forwardref-display-name | Problem | forwardRef components must have displayName | | stories-require-default-export | Problem | Story files must export Default | | props-must-be-visual | Suggestion | Props should be visual, not business logic | | react-import-named | Suggestion | Use named imports from React | | package-json-exact-deps | Problem | Enforce exact dependency versions | | github-actions-permissions | Problem | Enforce permissions and pinned action refs |

Examples

no-state-in-component-body

// ❌ State in component body
function Counter() {
  const [count, setCount] = useState(0);
  return <div>{count}</div>;
}

// ✅ State in custom hook
function useCounter() {
  const [count, setCount] = useState(0);
  return { count, setCount };
}

function Counter() {
  const { count } = useCounter();
  return <div>{count}</div>;
}

no-inline-jsx-functions

// ❌ Inline function
function Button() {
  return <button onClick={() => alert('clicked')}>Click</button>;
}

// ✅ Named function reference
function Button() {
  const handleClick = () => alert('clicked');
  return <button onClick={handleClick}>Click</button>;
}

classnames-required

// ❌ Ternary in className
<button className={isActive ? 'active' : 'inactive'}>Click</button>

// ✅ Use classNames utility
import classNames from 'classnames';
<button className={classNames({ active: isActive })}>Click</button>

Component Folder Structure

Each component should follow this structure:

ComponentName/
├── ComponentName.tsx              // Main component
├── ComponentName.types.ts         // TypeScript interfaces
├── ComponentName.hooks.ts         // Custom hooks (if needed)
├── ComponentName.stories.tsx      // Storybook stories
├── ComponentName.test.ts          // Tests
├── ComponentName.utils.ts         // Utilities (optional)
├── ComponentName.constants.ts     // Constants (optional)
└── index.ts                       // Exports

The index.ts must contain:

export { default as ComponentName } from "./ComponentName";
export * from "./ComponentName.types";

Notes

  • Rules 14 & 15: package-json-exact-deps and github-actions-permissions perform file-content parsing and are best used with appropriate parsers configured in your ESLint setup.
  • All rules support configuration options. See individual rule docs for details.