The Graph
As you use atoms inside an ecosystem, Zedux tracks dependencies and forms a "Weighted Directed Acyclic Graph" (...we just call it a graph) that it uses to efficiently propagate state updates.
- What the graph looks like.
- How to view the graph.
- The difference between static and dynamic graph edges.
- How to create the different types of graph edges.
- How to turn static graph edges into dynamic ones.
Graph Basics
Every node in an ecosystem's graph is either:
- An atom instance
- An AtomSelector (we'll learn about these in the selectors walkthrough)
All external dependents like React components also create a "pseudo-node" that won't show up in most graph views.
Every atom graph has one or more:
- "root" nodes - nodes that have no dependencies.
- "leaf" nodes - nodes that have no internal dependents.
Note that leaf nodes can have external dependents (e.g. React components). We usually ignore these pseudo-nodes when inspecting the graph.
Views
Zedux provides 3 simple graph "views" out of the box:
- Top-Down - An object containing every root node in the graph. Each node's value is an object containing its dependents who, in turn, contain their dependents, and so on till the leaf nodes.
- Bottom-Up - The inverse of Top-Down. An object containing every leaf node in the graph. Each node's value is an object containing its dependencies who, in turn, contain their dependencies, and so on till the root nodes.
- Flat - An object containing every node in the graph in the top level (no nesting). Each node has a list of dependency strings and a list of dependent strings that point to other keys in the top-level object. This is the only view that shows pseudo-nodes and is the best view for programmatically working with the graph.
Getting the Graph
Call ecosystem.viewGraph('top-down')
, ecosystem.viewGraph('bottom-up')
, or ecosystem.viewGraph('flat')
. Flat is the default.
Every ecosystem also has a ._graph
property that references an instance of the internal Graph class. This property is public, but underscore-prefixed to indicate that you probably shouldn't use it, but can if you need.
Edges
Every time you inject an atom in another atom, you create a dependency on the injected atom. When this happens, Zedux draws an "edge" in the graph to connect the two nodes.
Not all edges are created equal! Some dependencies behave differently depending on how the edge was created.
Dynamic Edges
When an edge is dynamic, the dependent will update every time the dependency's state changes. This means that if the dependent is a React component, it will rerender every time the dependency's state changes. If the dependent is an atom instance or selector, it will reevaluate every time the dependency's state changes.
We've seen one hook that creates dynamic edges, useAtomState()
:
const greetingAtom = atom('greeting', 'Hello, World!')
function Greeting() {
// this component will rerender every time greetingAtom's state changes
const [greeting, setGreeting] = useAtomState(greetingAtom)
...
}
The injector equivalent of useAtomState()
- injectAtomState()
- creates a dynamic edge between two atom instances.
const parentAtom = atom('parent', 'foo')
const childAtom = atom('child', () => {
// this atom will reevaluate every time parentAtom's state changes
const [parent, setParent] = injectAtomState(parentAtom)
...
})
Static Edges
When an edge is static, the dependent will not update when the dependency's state changes. So... what's the point of a static edge then?
Static edges inform Zedux that someone depends on the injected atom instance. As long as an atom instance has any dependents, Zedux won't try to clean it up. We'll learn more about Zedux's automatable cleanup in the destruction walkthrough.
Static dependents are informed when their dependency is force-destroyed. In this case, the static dependent actually will schedule a reevaluation or rerender to create a new instance. Again, we'll learn more in the destruction walkthrough.
Static dependents are also informed when their dependency's promise changes. More on promises in the suspense walkthrough.
We've seen one hook that creates static edges - useAtomInstance()
:
const greetingAtom = atom('greeting', 'Hello, World!')
function Greeting() {
// this component will _not_ rerender when greetingInstance's state changes
const greetingInstance = useAtomInstance(greetingAtom)
...
}
Other Properties
Zedux also tracks whether an edge was created implicitly or explicitly and whether an edge was created internally or externally. You won't typically need to worry about these, though you might see them show up in dev tools (e.g. you might see an edge labelled as "implicit-internal-dynamic" or "explicit-external-static" or any combination of those flags).
useAtomValue
A new hook! This is the simplest way to create a dynamic graph edge in a React component.
import { useAtomValue } from '@zedux/react'
const someAtom = atom('some', () => 'my state')
function MyComponent() {
// these two lines are exactly equivalent:
const val = useAtomValue(someAtom)
const [state] = useAtomState(someAtom)
}
injectAtomValue
As you might have guessed, useAtomValue
has an injector equivalent for easily creating dynamic graph edges in atoms.
injectAtomInstance
useAtomInstance()
also has an injector equivalent. injectAtomInstance()
is the simplest way to create static graph edges between atoms.
Dynamicizing Edges
(Yes, it's a word). Here's a common situation: You need an atom instance (e.g. to access its exports or promise), but want the component to rerender every time the atom instance's state changes.
Turns out, you can pass an atom instance directly to any hooks/injectors that expect an atom template (useAtomValue
, useAtomState
, injectAtomValue
, and injectAtomState
). When you do this, Zedux upgrades the edge from static to dynamic.
Inside atoms, the edge is upgraded. In React, Zedux adds another edge. This is because React doesn't currently provide a way to know that 2 hooks were used from the same component instance.
(Yes, we just made a simple todo app in ~40 lines of code!)
Staticizing Edges
(Also a word). useAtomInstance()
and injectAtomInstance()
also have overloads for passing atom instances.
You can't downgrade an edge from dynamic to static. But sometimes you'll receive atom instances from outside the current component or atom. In this case, useAtomInstance
/injectAtomInstance
can be used to register a static graph edge (which is an upgrade from nothing!).
You won't typically need to do this.
Instances as Params
As a general rule, all atom params must be serializable. There is one exception: You can pass an atom instance to another atom instance.
When an atom instance receives another atom instance via params, it doesn't create any kind of dependency on that instance. This is usually fine: Whatever passed the instance is probably already registering its own dependency on the instance.
This example passes the instance to injectAtomValue()
to create a dynamic dependency on the instance. To create a static dependency instead, use injectAtomInstance()
.
See AtomInstance#params for more info.
Recap
- View the graph with
ecosystem.viewGraph('top-down' | 'bottom-up' | 'flat')
- Create dynamic dependencies (or "edges") with
useAtomState
oruseAtomValue
and their injector equivalents. - Create static dependencies with
useAtomInstance
/injectAtomInstance
. - Upgrade an edge from static to dynamic by passing the instance to a dynamic hook/injector.
- Atom instances can be passed directly to other atoms as params.
Next Steps
With this newfound graph-building knowledge, it's time to learn to learn to swap out atom instances with atom overrides.