kea

Batteries-included state management for React, built on Redux with hooks, plugins, and automatic mount/unmount lifecycles.

Framework
npm
v3.1.7
1,991stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
46/100Fair
Development Activity28
Maintenance0
Community56
Maturity60
Momentum40

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
81/100Excellent
Architecture82
Code Quality78
Innovation74
Learning Curve90

Kea is a state management framework for React that treats “logic” — not components — as the fundamental unit of application state. A logic is declared once with composable builder functions (actions, reducers, selectors, listeners) and is lazily built and mounted the first time a component uses it, then automatically unmounted when the last consumer unmounts, all backed by a single shared Redux store under the hood.

Beyond the state container itself, Kea ships hooks (useValues, useActions, useMountedLogic) that tie logic lifecycle directly to React component lifecycle, dynamic “keyed” logic for per-instance state (e.g. one logic definition reused across list items), and built-in listeners with breakpoints for handling async race conditions without a separate saga library. A plugin hook system running at each build stage lets the wider Kea ecosystem (kea-router, kea-loaders, kea-forms, and others) extend every built logic instance uniformly, which is how larger consumers like PostHog structure their entire front-end state layer around it.

What You Get

  • A kea() logic wrapper that lazily builds and caches state per component, with reference-counted mount/unmount tied to React component lifecycle
  • React hooks (useValues, useActions, useAsyncActions, useMountedLogic, useSelector) plus BindLogic/Provider for scoping logic in the component tree
  • Built-in listeners and breakpoint support for async race-condition handling, without needing a separate saga or effect library
  • Dynamic keyed logic — one logic definition instantiated many times by key/props, useful for per-row or per-entity state
  • A plugin hook system (beforeBuild/afterBuild/beforeLogic/afterLogic) that lets ecosystem packages like kea-router and kea-loaders extend every logic instance uniformly
  • Full TypeScript typing for logic inputs, built logic, and selectors, verified at compile time via tsd in addition to runtime Jest tests

Common Use Cases

  • Central application state for a mid-to-large React app, replacing hand-rolled Redux boilerplate with composable logic builders
  • Feature-scoped state that should mount only while a specific screen or component is on-screen and unmount automatically after
  • Per-item dynamic state (e.g. a logic per list row or per modal instance) using Kea’s keyed logic
  • Async data-fetching flows that need cancel-if-stale semantics via breakpoint, without pulling in redux-saga
  • Extending shared state behavior across an app via Kea plugins (routing sync, loaders, forms) instead of duplicating that logic in every feature

Under The Hood

Architecture Kea’s code is organized into three layers: kea/ (build, mount, context, plugins, store, reducer), core/ (actions, reducers, selectors, connect, listeners, events, path, props, key, defaults), and react/ (hooks, bind, wrap, provider). A LogicWrapper created by kea(input) (src/kea/kea.ts) lazily builds a BuiltLogic via getBuiltLogic (src/kea/build.ts), caching instances by key/props in a per-wrapper context and applying an ordered pipeline of composable “logic builders” through a plugin hook system (runPlugins in plugins.ts) that fires beforeBuild/beforeLogic/afterLogic/afterBuild at each stage, letting ecosystem plugins (kea-router, kea-loaders) inject fields onto built logic without touching core. Mounting (mount.ts) attaches/detaches a reducer to one shared Redux store with reference counting, and React bindings (react/hooks.ts) tie this lifecycle to component mount/unmount via useMountedLogic, batching store updates with batchChanges. Selectors are memoized with reselect, and connect() wires cross-logic dependencies through a build heap resolved depth-first. Because so much ecosystem tooling hooks into this exact build pipeline and hook ordering, changing it has wide downstream impact.

Tech Stack TypeScript throughout under strict mode, compiled with Rollup (plus rollup-plugin-dts) into dual CJS/ESM output with generated type declarations. Runtime dependencies are Redux for the store, reselect for memoized selectors, and use-sync-external-store as a React-18-safe subscription shim, with React itself as a peer dependency. Dev tooling includes Babel (with a long tail of legacy proposal plugins), Jest with jsdom and Testing Library for unit tests, tsd for compile-time type tests, ESLint/Prettier enforced via husky and lint-staged, and a GitHub Actions workflow running the suite. The package manager is pinned to pnpm.

Code Quality Test coverage is extensive and multi-tiered: dozens of Jest test files exercise nearly every core module (mount, connect, listeners, reducers, selectors, hooks, plugins, context, async actions, strict mode), a separate tsd suite verifies types at compile time, and the test script additionally runs a full tsc typecheck. Source code uses explicit generics for the Logic/BuiltLogic/LogicWrapper types and descriptive, prefixed runtime errors instead of silent failures, though one helper does swallow exceptions in a bare catch block to guarantee a counter is decremented, discarding the underlying error. Naming is consistent, and lint/format/CI are all enforced automatically.

What Makes It Unique Kea’s distinguishing choice is making “logic” itself — not components — the unit of state, built via composable builder functions and mounted lazily on first use, then reference-counted and torn down automatically when the last consumer unmounts, unlike libraries where a store is typically created eagerly and lives for the app’s lifetime. Its plugin hook system lets ecosystem packages extend every logic instance uniformly, and its keyed dynamic logic (one definition, many instances) is exposed as a first-class primitive that few comparable libraries offer directly, alongside built-in listeners and breakpoints for async race-condition handling without a separate effects library.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search