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

@noctcore/eslint-plugin-react

v0.4.2

Published

React architecture and correctness ESLint rules: prop drilling, state colocation, memoized context, effect safety and guarded web storage.

Readme

@noctcore/eslint-plugin-react

React architecture and correctness rules: unmemoized context values, effects without cleanup, state derived in effects, and components that grow past what one file should hold.

Docs: noctcore.github.io/eslint-plugins/packages/react

Not a good fit for a codebase that keeps hooks inline by convention, or a library of generic primitives where wide prop surfaces are the point.

Requirements

  • ESLint 9 or newer, flat config (eslint.config.js) only.
  • configs.recommended registers the plugin and sets rule severities, nothing else. It sets no files and no parser, so it applies to whatever files the rest of your config lints. To lint TypeScript, add a files pattern and @typescript-eslint/parser, as in the quick start.

Install

bun add -D @noctcore/eslint-plugin-react @typescript-eslint/parser   # or npm i -D / pnpm add -D

Quick start

// eslint.config.js
import tsParser from '@typescript-eslint/parser';
import react from '@noctcore/eslint-plugin-react';

export default [
  {
    ...react.configs.recommended,
    files: ['**/*.{ts,tsx}'],
    languageOptions: { parser: tsParser },
  },
  // Optional: tune a preset rule's options.
  {
    files: ['**/*.{ts,tsx}'],
    rules: {
      'noctcore-react/no-prop-drilling': ['error', { maxForwarded: 4 }],
    },
  },
];

Rules

✅ on in recommended (error) · 🔘 opt-in: not on in recommended, enable it yourself · ⚙️ needs options · 🔧 --fix · 💡 suggestions · 💭 needs type info · ❌ deprecated

| Rule | Description | Preset | ⚙️ | 🔧 | 💡 | 💭 | | --- | --- | :-: | :-: | :-: | :-: | :-: | | component-props-naming | A function component's first-param props type must be named <Component>Props. Autofixes by renaming the in-file type declaration and its references when that is safe. | ✅ | | 🔧 | | | | context-value-must-be-memoized | A context Provider value must not be an inline object literal — a fresh reference each render re-renders every consumer. Memoize it with useMemo and pass the stable reference. | ✅ | | | | | | max-hook-return-surface | An exported use* hook in a hook file may return object literals (top-level or nested one level) of at most max (default 20) members. Wider returns are god-controllers in the making. | ✅ | | | | | | max-hooks-per-file | A hook/query/mutation file (default *.hooks.ts / *.queries.ts / *.mutations.ts) may export at most max (default 4) use* hooks. Split larger files. | ✅ | | | | | | max-props-per-component | A *Props interface/type-literal may declare at most max (default 12) local members. extends clauses are not counted. | ✅ | | | | | | no-effect-derived-state | An effect whose entire body is setState calls with values derived purely from its dependencies is the "you might not need an effect" anti-pattern — compute the value during render (or with useMemo) instead. | ✅ | | | | | | no-jsx-computation | Disallow array methods, arithmetic, and chained logical expressions directly inside JSX {...}. Lift them to a const above the return or into the hook. | ✅ | | | | | | no-jsx-in-hooks | A use*-named function must not return JSX — that is a component wearing a hook costume. Rename it to a PascalCase component, or return values instead of elements. | ✅ | | | | | | no-prop-drilling | A bundle of maxForwarded+ (default 4) props each forwarded unchanged (name={name}) from the *Props param to the same child component is prop drilling. Compose instead. | ✅ | | | | | | no-state-in-component-body | State/effect/query hooks must live in the colocated hook file (<Name>.hooks.ts), not in the component .tsx body. The component is a thin shell. | ✅ | | | | | | no-unguarded-web-storage | A localStorage / sessionStorage call must sit inside a try block: the access itself throws when storage is disabled, partitioned or full. | ✅ | | | 💡 | | | prefer-lazy-state-init | A useState initializer that is an expensive call (default JSON.parse / localStorage.getItem / sessionStorage.getItem, or a configured builder) must be wrapped in a function so it runs once on mount, not every render. | ✅ | | 🔧 | | | | props-must-be-visual | Component props must be visual. Auth/business-identity and credential prop names (userId, currentUser, *token*, *jwt*, *secret*, apiKey, ...) are disallowed. A live password input is a legitimate visual concern and is intentionally allowed. | ✅ | | | | | | require-effect-cancellation | A setState/dispatch that runs after an await or .then() inside a useEffect must be guarded against a unmounted/re-run effect (AbortController, a cancelled flag, or a cleanup return). | ✅ | | | | |

Severity policy

Every rule is error or off, never warn: a warning is a rule nobody obeys. See the severity policy.