Skip to content

Ownership & cleanup

Source inputLifetime
Existing XState actor, Redux store, MobX observableBorrowed and shared; application owns native shutdown
XState machine, Redux sliceIsolated runtime instances acquired when used
Redux store factory from ignite-element/reduxIsolated store per instance
MobX factory from ignite-element/mobxIsolated observable per instance

One core serves one source/runtime contract.

Multiple registered names or hooks may intentionally share that contract.

Create the source and core outside framework rendering.

When you pass an existing XState actor, Redux store or MobX observable, the core is ready after construction.

Pass that core directly to useIgnite(core).

No preparation read is required.

See the shared React example.

Machine/slice/fresh-factory inputs create a private runtime per element or React/React Native hook.

The synchronous hook needs no preparation-only read.

Explicit get/watch/on/execute operations use a separate headless runtime, never an arbitrary hook’s state.

Independent factories and initialization must be safe to repeat and discard without external work or resources requiring cleanup.

This includes XState context initialization, Redux reducer/middleware/enhancer initialization, MobX construction, projections and command setup.

A factory returning a singleton is not independent.

Ignite starts owned actors, observation and effects only when a hook commits, through observation or a command issued from a layout effect or callback ref.

Construction alone starts none of them.

watch(...) and on(...) each return an independently idempotent { unsubscribe() }.

End a temporary observer when its consumer leaves.

An independent useIgnite(core) releases its private runtime automatically on unmount.

A genuine new mount starts fresh; synchronous Strict Mode replay keeps the live runtime.

Web Activity hiding retains state, commands and already-active source resources; view subscriptions and independent projection effects pause.

See Compatibility for the tested web and native boundaries.

Removal or core replacement releases the retained runtime, and terminal core disposal also drains hidden runtimes.

A shared hook releases only its own subscription.

Neither unmount disposes the reusable core or shuts down a borrowed source.

No cleanup useEffect is needed in that component.

The core remains available for other views or a later mount.

True element disconnect releases the view’s forwarding subscription; reconnect acquires a fresh one.

Same-tick moves retain it.

A source update while no view is present does not become a replayed occurrence on reconnect.

Shared adapters and activated effects remain observed through zero-view intervals until terminal core disposal.

Disconnecting a view releases its subscriptions without ending application-level observation or granting native source ownership.

The cleanup option is removed; supplying it, including false or undefined, is an error.

See the migration note and effect activation and timing.

Call core.dispose() when you permanently discard that core.

For example, a feature or signed-in session can own a core and dispose it when that lifetime ends.

Disposal is terminal, including for cores with registered custom elements.

It releases Ignite-owned observation, events, effects, projections, bindings, and command windows.

Registered definitions remain; later connections are inert.

Suppose the shared counter belongs to a signed-in session.

Navigating away from its screen only unmounts the views.

At sign-out, stop views from using that session’s core before disposing it.

Use the same module shown in Views:

Session owner imports
import { core, source } from './counter-core';

Place this code inside the session’s existing end handler:

Inside the session-end handler
try {
core.dispose();
} finally {
source.stop();
}

Here the session created and exclusively owns the actor, so it also stops that actor.

If another feature still needs the actor, dispose only this core and leave actor shutdown to its owner.

For an Ignite-created private actor, core.dispose() also releases that actor.

A later session creates a new core; a disposed core cannot be reused.

Leaving a screen does not necessarily end its session.

Backgrounding a native app does not necessarily end its core’s lifetime either.

A core retained for the application’s lifetime can stay available throughout normal navigation.

Do not rely on a tab-close or process-termination callback for essential work.

What ends?Who handles cleanup?
One React or React Native componentuseIgnite releases its private runtime, or only its view subscription for a shared source
The feature or session that owns the coreIts existing lifecycle code calls core.dispose()
An externally created XState actorIts owner calls actor.stop() when nothing else needs it
An Ignite-created private XState actorIgnite stops it when the owning runtime is released, including core disposal

Disposal prevents new resource acquisition.

It attempts every release and rethrows the first exact cleanup error.

Repeated disposal is inert even if the first call threw.

Live reads, subscriptions, commands, and bindings reject afterward; catalogue reads remain available.

Pending effect evaluations are discarded.

Borrowed sources remain application-owned.

For an application-owned XState actor, call core.dispose() inside try and source.stop() in finally.

This stops the source even if observation cleanup throws.

An Ignite-created private XState actor is stopped exactly once after its observations drain; an unused core does not create one.

Disposal does not cancel entered business I/O or revoke a completed operation.

Keep cancellation, persistence, freshness, and durable receipts with the source.

For an application with ports and session lifetimes, see the advanced shared-source recipe.