Skip to main content

ZeduxPlugin

The base class of Zedux ecosystem plugins.

A plugin can turn on ecosystem mods and hook into mod events. Mod events are just action objects containing info about stuff happening inside the ecosystem.

Plugins set up a sort of bidirectional stream with ecosystems:

  • Plugins expose a modStore for ecosystems to subscribe to. This allows plugins to dynamically turn the mods they need on and off.
  • Ecosystems expose a modBus for plugins to subscribe to.

This class also has a single static property - actions. This is an object mapping every available mod to an action factory that Zedux uses to dispatch mod events to plugins.

Live Sandbox
12
// Easy way to see all available mods:
const allMods = Object.keys(ZeduxPlugin.actions)
note

Mods have some overhead. That's why they're disabled by default. Turning them all on is probably not recommended in production builds of bigger apps. Dev tools might want them all on and that's probably fine in development.

Example

const plugin = new ZeduxPlugin({
initialMods: ['stateChanged'],

registerEcosystem: ecosystem => {
const subscription = ecosystem.modBus.subscribe({
effects: ({ action }) => {
if (action.type === ZeduxPlugin.actions.stateChanged.type) {
// handle stateChanged mod event
}
},
})

return () => subscription.unsubscribe()
},
})

const ecosystem = createEcosystem({ id: 'root' })
ecosystem.registerPlugin(plugin)

This should seem a little busy compared to most of Zedux's APIs. There is lots of room for abstracting some functionality, but for now we're keeping plugins intentionally low-level.

Creating

Plugins are a little lower-level. This is the only class in Zedux that you insantiate using the new keyword.

const myPlugin = new ZeduxPlugin()

The constructor takes an optional config object:

{ initialMods, registerEcosystem }
initialMods

Optional. An array of mod strings.

These don't need to be set initially. You can modify this list at any time by changing the state of your plugin's modStore:

const plugin = new ZeduxPlugin()
plugin.modStore.setState({ stateChanged: true })
// This is equivalent to:
const plugin = new ZeduxPlugin({ initialMods: ['stateChanged'] })
registerEcosystem

Optional. A function that receives the ecosystem and can do anything with it.

Signature:

registerEcosystem = (ecosystem) => optionalCleanup

You can return a cleanup function to clean up subscriptions when the plugin is unregistered.

This is where you do all the plugin magic. Almost all plugins will pass this.

If your plugin turns on any mods, you'll likely want to subscribe to the ecosystem's modBus to handle those mod events.

Properties

Every ZeduxPlugin has just two properties:

modStore

A Zedux store.

This store's state is an array of mod names. If a mod is in this list, it will be turned on in ecosystems where this plugin is registered. By default, this array is empty, unless you pass a list of initialMods.

You can change this store's state at any time to turn mods on/off in the ecosystem. When no plugins depend on a given mod, Zedux turns it off.

registerEcosystem
A reference to the registerEcosystem function passed to the ZeduxPlugin constructor or a no-op function if none was passed.

Mod Details

ecosystemWiped

Turn this mod on to receive a mod event when the ecosystem is wiped (usually as part of a reset).

Payload shape:

{
ecosystem: Ecosystem
}
edgeCreated

Turn this mod on to receive a mod event every time an edge is created in the ecosystem's graph. This edge represents a dependency between two nodes in the graph.

This mod can also be used to determine when an edge has "moved" after a dependency was force-destroyed. Since no edgeRemoved events are sent for edges between a force-destroyed node and its dependents, a plugin's internal graph representation would still have those edges intact when the new edgeCreated events come in after the dependency is recreated. These edgeCreated events will thus appear to be duplicates. When this happens, you can infer that the edge was "moved".

Payload shape:

{
dependency: AnyAtomInstance | SelectorCache
dependent: AnyAtomInstance | SelectorCache | string
edge: DependentEdge
}

dependent will be a string if it's a "pseudo-node" in the graph, representing an external dependent like a React component.

You'll usually pair this mod with edgeRemoved.

edgeRemoved

Turn this mod on to receive a mod event every time an edge is removed from the ecosystem's graph. This edge represents a dependency between two nodes in the graph.

Payload shape:

{
dependency: AnyAtomInstance | SelectorCache
dependent: AnyAtomInstance | SelectorCache | string
edge: DependentEdge
}

dependent will be a string if it's a "pseudo-node" in the graph, representing an external dependent like a React component.

You'll usually pair this mod with edgeCreated.

evaluationFinished

Turn this mod on to receive a mod event every time an atom instance or selector in the ecosystem finishes evaluating. This mod makes Zedux track evaluation time of every atom instance and selector.

Payload shape:

{
node: AnyAtomInstance | SelectorCache
time: number
}

node is either an instance of AtomInstance or SelectorCache.

time will be a DOMHighResTimestamp measured in milliseconds.

Note that this uses performance.now() when available in the global scope. In other envs, it will fall back to using a non-high-res timestamp via Date.now().

instanceReused

Turn this mod on to receive a mod event every time an existing atom instance is reused anywhere.

This mod might be useful to find problematic duplicate atom keys. Though HMR/React Fast Refresh complicates this. Plugins can ignore overrides created via template.override() by checking template._isOverride.

Payload shape:

{
instance: AnyAtomInstance
template: AnyAtomTemplate
}

instance references the existing atom instance being reused. template is the atom template used to find the instance on this particular call.

stateChanged

Turn this mod on to receive a mod event every time an atom instance's state or an atom selector's cached result changes.

Payload shape:

{
action?: ActionChain
cache?: SelectorCache
instance?: AnyAtomInstance
newState: any
oldState: any
reasons: EvaluationReason[]
}

Either cache or instance will always be set.

If the atom or selector reevaluated due to this state change, reasons will be set to the list of EvaluationReasons explaining the evaluation. Otherwise it will be empty.

statusChanged

Turn this mod on to receive mod events every time an atom instance's status property transitions to a new state (e.g. from Initializing to Active or from Active to Stale).

This mod also triggers messages when selector caches are finished initializing and when selector caches are destroyed. Selectors don't track their lifecycle like atom instances do, but the newStatus and oldStatus fields for these events will be LifeCycle strings exactly like the strings used for atom instances.

Payload shape:

{
node: AnyAtomInstance | SelectorCache
newStatus: LifecycleStatus
oldStatus: LifecycleStatus
}

node will be either an atom instance or an atom selector. You can use is() to determine which.

oldStatus will be one of "Active", "Initializing", or "Stale"

newStatus will be one of "Initializing", "Stale", or "Destroyed"

Note that atom selectors can never be "Stale". Their lifecycle essentially goes Initializing -> Active -> Destroyed.

See Also