Skip to content

effector/no-units-spawn-in-render ​

Forbids creating Effector units or calling operators inside React components or hooks.

Creating units in render leads to:

  • New units created on every render, causing memory leaks
  • Subscriptions and relationships being recreated unnecessarily
  • Unpredictable application state

Examples ​

Invalid ​

tsx
// 👎 Units created inside component will be recreated on every render
function Component() {
  const $store = createStore(0)
  const clicked = createEvent()

  sample({ clock: clicked, target: $store })

  return <button onClick={clicked}>click</button>
}

// 👎 Units inside hooks (useMemo, useEffect, useCallback) are still problematic
function Component() {
  const $store = useMemo(() => createStore(0), [])

  useEffect(() => {
    sample({ clock: clicked, target: $store })
  }, [])

  return <div>hello</div>
}

// 👎 Custom factory functions that return units are also forbidden
function createModel() {
  const $store = createStore(0)
  return { $store }
}

function Component() {
  const model = createModel()
  return <div>hello</div>
}

// 👎 Same rules apply to custom hooks
function useMyStore() {
  return useMemo(() => createStore(0), [])
}

Valid ​

tsx
// 👍 Units created at module level (outside components)
const $store = createStore(0)
const clicked = createEvent()

sample({ clock: clicked, target: $store })

function Component() {
  const value = useUnit($store)
  const click = useUnit(clicked)

  return <button onClick={click}>{value}</button>
}

// 👍 Units accessed via useContext are OK
const ModelContext = createContext({ $store })

function Component() {
  const { $store } = useContext(ModelContext)
  const value = useUnit($store)

  return <div>{value}</div>
}

// 👍 effector-factorio's useModel() retrieves units from context, not creating new ones
import { counterFactory } from "./model"

function Counter() {
  const { $count, inc } = counterFactory.useModel()
  const count = useUnit($count)

  return <button onClick={inc}>{count}</button>
}

Rule Details ​

This rule detects:

  1. Direct Effector API calls: createStore, createEvent, createEffect, createDomain, createApi, restore
  2. Effector operators: sample, guard, forward, merge, split, combine, attach
  3. Custom factory functions: Functions that return objects containing Effector units (requires TypeScript)

The rule uses TypeScript type information to detect custom factories that return Effector units.

Configuration ​

detectCustomFactories ​

Controls whether the rule detects custom factory functions (functions that return objects containing Effector units). Defaults to true.

Disable custom factory detection entirely:

jsonc
// Only flag direct Effector API calls and operators — ignore custom factories
"effector/no-units-spawn-in-render": ["error", { "detectCustomFactories": false }]

Allow specific functions (allowlist):

jsonc
// Detect custom factories, but skip these specific function names
"effector/no-units-spawn-in-render": ["error", {
  "detectCustomFactories": { "allowlist": ["useModel", "getViewModel"] }
}]

The allowlist matches against the resolved callee name — "getModel" will skip both getModel() and obj.getModel().

Known Exceptions ​

effector-factorio ​

The effector-factorio library provides a factory.useModel() hook that retrieves pre-created units from React context (similar to useContext). This rule has a built-in exception for useModel — it will not be flagged.

Note that factory.createModel() will still be flagged if called inside a component, since it creates new unit instances.

When Not To Use It ​

If you have a specific use case where creating units dynamically is intentional and properly managed (e.g., in a factory pattern with proper cleanup), you may disable this rule for that specific case.

Last updated:

Released under the MIT License