Files
zeekay 1b367e2b62 chore: rebase the tree onto upstream master
The tree was a debranded snapshot of upstream taken around 2026-02-20, and
upstream has moved about five months since. This replaces it with upstream
master, mechanically re-derived: bin/debrand does the rename, and the
pipeline and squashed migrations are restored from the shipped tree.

Not a merge. The previous import replaced the tree wholesale in a single
commit, so there is no shared content history to merge against. Reproducing
the rename on the new tree is what actually converges.

What survives: .hanzo/workflows, the Dockerfiles and bin/docker* entrypoints,
compose files, LLM.md, and insights/migrations. The squashed initial
migration is load-bearing — the live database records it by name, so
upstream's ~918 individual migrations cannot come back.

Nine products are dropped as unreferenced. Twenty-one more that we do not run
are kept anyway because the core app still imports them; unpicking those is a
refactor and does not belong in a rebase.

This base does not build yet, and the reasons are not in the rename:
upstream now wants Python 3.13.13 against our 3.12.12 and Django 5.2 against
our 4.2, and the dependency manifests still name upstream packages that we
republish under our own names. Those are decisions, not renames.
2026-08-01 15:44:40 -07:00

4.1 KiB

Connecting logics and React

Two parts to this: kea-to-kea (connect, imperative mounting) and kea-to-React (useValues, useActions, BindLogic).

Pulling values and actions from another logic — connect

connect(() => ({
  values: [teamLogic, ['currentTeamId', 'currentTeam'], userLogic, ['user']],
  actions: [teamLogic, ['loadCurrentTeam']],
  logic: [eventUsageLogic], // mount-only, no values pulled
}))
  • Always use the function form connect(() => ({...})). Defers evaluation past module load, so circular imports don't blow up.
  • values: [logic, ['a', 'b'], otherLogic, ['c']] flattens — each logic followed by its names. The names become available on values inside the logic.
  • actions: [logic, ['x']] lets you both dispatch actions.x() and listen for x in your own listeners.
  • logic: [otherLogic] mounts the logic without pulling anything. Use when you need the other logic running for its side effects (e.g. analytics, polling).

Connecting to a keyed logic

connect((props: FooLogicProps) => ({
  values: [barLogic(props), ['something']],
}))

Take props and pass them through to the keyed logic. Same props → same key → same instance, so you talk to the same one as everyone else with the same props.

Mounting another logic from inside a logic

When you need a logic's instance (to track it, find it, or hand it to something), use .build() + .mount():

listeners(({ actions, cache }) => ({
  openInsight: ({ insightId }) => {
    const logic = insightLogic.build({ dashboardItemId: insightId })
    const unmount = logic.mount()
    cache.disposables.add(() => unmount, `insightRef:${insightId}`)
  },
}))
  • .build(props) returns the wrapper without mounting.
  • .mount() mounts it and returns an unmount function.
  • Register the unmount with cache.disposables so it's cleaned up automatically.

findMounted — peek without mounting

const logic = otherLogic.findMounted({ id })
if (logic) {
  actions.doSomething(logic.values.x)
}

Returns undefined if the logic isn't mounted. Use when "if it happens to be running, sync with it; otherwise nothing".

Wiring up React — useValues / useActions

import { useActions, useValues } from 'kea'

function FooView({ fooId }: { fooId: string }): JSX.Element {
  const logic = fooLogic({ fooId }) // for keyed logics
  const { foo, fooLoading } = useValues(logic)
  const { setName } = useActions(logic)
  return <input value={foo?.name ?? ''} onChange={(e) => setName(e.target.value)} />
}

For singletons, drop the (props): useValues(fooLogic).

These hooks are how a component subscribes to a logic. useValues re-renders the component when the chosen values change; useActions returns stable action refs.

BindLogic — provide a keyed instance to a subtree

If a parent component knows the key and its children don't want to thread props everywhere, wrap them in BindLogic:

<BindLogic logic={fooLogic} props={{ fooId }}>
  <FooHeader /> {/* useValues(fooLogic) resolves to the bound instance */}
  <FooDetails />
  <FooFooter />
</BindLogic>

Inside the subtree, useValues(fooLogic) (no props) works because BindLogic set the context.

Nest BindLogic when you have multiple keyed dependencies:

<BindLogic logic={dataNodeLogic} props={dataNodeProps}>
  <BindLogic logic={dataVisualizationLogic} props={vizProps}>
    <SQLEditor />
  </BindLogic>
</BindLogic>

useMountedLogic — own the logic lifecycle

function FooHost(): JSX.Element {
  useMountedLogic(fooLogic) // mounts on mount, unmounts on unmount
  return <FooView />
}

Use when a logic should live exactly as long as a particular component, and no one else is going to mount it. useValues and useActions already mount as a side effect, so useMountedLogic is only needed when no values are pulled.

useAttachedLogic — keep a logic mounted through the scene

For scene-level child logics that must survive React unmounts, use useAttachedLogic.

Anti-patterns

See anti-patterns.md for the consolidated catalogue.