Atom Getters
This walkthrough has introduced several hooks and injectors that let you manipulate the dependency graph. These are easy enough. But it gets even easier.
How to use Atom Getters to dynamically and efficiently update the graph.
The Atom Getters
For your convenience when working with atoms, Zedux provides a single, uniform object structure in many places. Objects with this shape are called Atom Getters objects. Every Atom Getters object has the following 3 "Atom Getters":
get
getInstance
select
Most Atom Getters objects also have a fourth property:
ecosystem
- a reference to the current ecosystem.
Atom Getters behave differently depending on how and when they're used. Mastering these hows and whens will make Zedux an extremely efficient tool for you.
Let's take a quick look at each Atom Getter.
get
Returns the current value of an atom instance. Creates the instance using the passed template if it doesn't exist.
const value = get(myAtom)
// or, if the atom takes params:
get(myAtom, ['param 1', 'param 2'])
getInstance
Returns an atom instance. Creates the instance using the passed template if it doesn't exist.
const instance = getInstance(myAtom)
// or, if the atom takes params:
getInstance(myAtom, ['param 1', 'param 2'])
select
Runs an Atom Selector (we'll learn about these in the selectors walkthrough).
const selectorResult = select(myAtomSelector)
// or, if the Atom Selector takes params:
select(myAtomSelector, 'param 1', 'param 2') // note params isn't an array here
Atom Getter Habitats
Where exactly might one encounter Atom Getters in the wild?
Ecosystems
That's right, every ecosystem is an Atom Getters object.
const ecosystem = useEcosystem()
// get
const instanceValue = ecosystem.get(myAtom, ['param 1', 'param 2'])
// getInstance
const instance = ecosystem.getInstance(myAtom, ['param 1', 'param 2'])
// select
const selectorResult = ecosystem.select(myAtomSelector)
Ecosystems are the only Atom Getters objects that don't have an ecosystem
field (they don't need it of course!). The ecosystem's Atom Getters are also unique in that they never register graph dependencies. More on this shortly.
injectAtomGetters
You can inject an Atom Getters object in any atom using injectAtomGetters()
:
const exampleAtom = atom('example', () => {
const { ecosystem, get, getInstance, select } = injectAtomGetters()
...
})
Atom Selectors
The first argument passed to Atom Selectors is an Atom Getters object:
function myAtomSelector({ ecosystem, get, getInstance, select }: AtomGetters) {
...
}
(More on these in the selectors walkthrough).
Ions
The first argument passed to the state factory of Ions is an Atom Getters object:
const exampleIon = ion(
'example',
({ ecosystem, get, getInstance, select }) => {}
)
(We'll learn about these in the selectors walkthrough too).
Registering Dependencies
When get
, getInstance
, and select
are called synchronously during atom or selector evaluation, they register graph dependencies.
get
registers dynamic dependencies.getInstance
registers static dependencies.select
registers a dynamic dependency on the selector itself, which in turn registers its own dynamic and static deps.
This graph automation only happens during evaluation. When called after evaluation ends (e.g. in an effect or exported callback), Atom Getters never register graph dependencies.
The Atom Getters on the ecosystem also never register graph dependencies. You can use this to purposefully prevent dependencies from being registered:
// toggling this atom's value changes maybeDynamicAtom's dynamicity!
const subscribeAtom = atom('subscribe', true)
const maybeDynamicAtom = ion('maybeDynamic', ({ ecosystem, get, select }) => {
const subscribe = get(subscribeAtom)
const val = subscribe
? select(myAtomSelector) // dynamic
: ecosystem.select(myAtomSelector) // static
})
Static Evaluation
Since the ecosystem's Atom Getters never register graph dependencies, they can be used to statically create, evaluate, and analyze atom instances and Atom Selectors outside React (i.e. anywhere).
// create an atom instance outside React or atoms or anything:
const instance = ecosystem.getInstance(myAtom)
// run a selector anywhere:
const val = ecosystem.select(myAtomSelector)
The ecosystem's Atom Getters make it easy to test atoms and Atom Selectors!
Cheatsheet
Here's a Little Ultimate Atom Getters Cheatsheet of Which Atom Getters Register Dependencies When:
const exampleAtom = atom('example', () => {
const { ecosystem, get, getInstance, select } = injectAtomGetters()
// registers a dynamic graph dependency:
get(myAtom) // essentially an alias of injectAtomValue
// registers a static graph dependency:
getInstance(myOtherAtom) // essentially an alias of injectAtomInstance
// registers a dynamic dependency on myAtomSelector's result:
select(myAtomSelector) // essentially an alias of injectAtomSelector
// doesn't update the graph; just returns the current value:
ecosystem.get(myAtom)
// doesn't update the graph; just returns the current atom instance:
ecosystem.getInstance(myOtherAtom)
// doesn't update the graph; just returns the selector result:
ecosystem.select(myAtomSelector)
// effects always run later (after evaluation)
injectEffect(() => {
// doesn't update the graph; essentially an alias of ecosystem.get
get(myAtom)
// doesn't update the graph; essentially an alias of ecosystem.getInstance
getInstance(myOtherAtom)
// doesn't update the graph; essentially an alias of ecosystem.select
select(myAtomSelector)
}, []) // AtomGetters are stable references - no need to pass them here
// callbacks are usually called after evaluation, but *it depends!*
const myCallback = injectCallback(() => {
get(myAtom)
getInstance(myOtherAtom)
select(myAtomSelector)
}, [])
// calling the callback here makes all its Atom Getter calls register deps:
myCallback()
return api().setExports({
myCallback, // dependents that call this callback will *not* register deps
})
})
Use ecosystem.get
, ecosystem.getInstance
, and ecosystem.select
to purposefully avoid creating dependencies during evaluation.
Buffered Updates
While an atom instance or Atom Selector is evaluating, Zedux buffers graph updates caused by Atom Getters and flushes the buffer once evaluation is over. This has a few implications:
- It means that you won't actually see graph updates happen immediately:
// bad:
const exampleAtom = atom('example', () => {
const { ecosystem, get } = injectAtomGetters()
const otherVal = get(myOtherAtom) // create a dep
ecosystem.viewGraph() // the dep won't be there (it doesn't exist yet!)
})
// good:
const exampleAtom = atom('example', () => {
const { ecosystem, get } = injectAtomGetters()
const otherVal = get(myOtherAtom) // create a dep
injectEffect(() => {
ecosystem.viewGraph() // effects are deferred; the dep exists now!
}, [])
})
- It allows Zedux to be highly efficient with its graph updates:
const exampleAtom = atom('example', () => {
const { get, getInstance } = injectAtomGetters()
// registers a dynamic graph dependency on myAtom:
const someField = get(myAtom).someField
// doesn't register anything! (We've already registered this dep):
const anotherField = get(myAtom).anotherField
// also registers nothing (can't downgrade an edge from dynamic to static):
const instance = getInstance(myAtom)
})
Conditional Injectors
Remember how injectors are like hooks? As such, injectAtomValue
, injectAtomInstance
, etc. can't be used in if
statements or loops. Atom Getters, however, are not injectors. This means they can be used conditionally!
Weak Getters
Just like hooks and injectors, the get
and getInstance
Atom Getters create the atom instance if it doesn't exist yet. This is almost always what you want. But in rare cases, you may want to see if the atom instance exists before creating it. Or you may not need the atom instance at all if it hasn't been created somewhere else.
For these use cases, ecosystems (and only ecosystems) have 2 "Weak Getters":
find
Returns an atom instance if the atom instance exists. Otherwise returns undefined.
const instance = ecosystem.find(myAtom)
const paramsExample = ecosystem.find(myAtom, ['param 1', 'param 2'])
const fuzzySearchString = ecosystem.find('myAtomKey')
When a string is passed, ecosystem.find()
returns the first atom instance it finds whose id contains the passed string (case-insensitive).
findAll
Returns an object containing all instances of the passed atom, keyed by each instance's id.
const instances = ecosystem.findAll(myAtom)
const fuzzySearchString = ecosystem.findAll('myAtomKey')
When a string is passed, ecosystem.findAll()
returns all atom instances whose id contains the passed string (case-insensitive).
This can make finding your atoms a breeze. For example, if you're creating a dashboard component, give your atoms keys like dashboardFilter
, dashboardSelection
then use ecosystem.findAll('dashboard')
to find all the atom instances you're working with.
Pass nothing to ecosystem.findAll()
to get every atom instance in the ecosystem.
const allAtomInstances = ecosystem.findAll()
Recap
get
returns an atom instance's value. It registers dynamic graph dependencies when called synchronously during evaluation.getInstance
returns an atom instance. It registers static graph dependencies when called synchronously during evaluation.select
returns an Atom Selector's result. It registers dependencies dynamically depending on the Atom Selector.- Zedux passes an Atom Getters object to Atom Selectors and Ions. Or get them manually with
injectAtomGetters()
. Every ecosystem is also an Atom Getters object. - The ecosystem's Atom Getters never register graph dependencies - and thus are your static analysis and testing friends.
- When called after evaluation, Atom Getters do not register graph dependencies.
- Atom Getters can be safely used in loops and conditional statements.
- The Weak Getters (
find
andfindAll
) never create atom instances.
Next Steps
Alright, alright, we keep hearing about these Atom Selector things. Let's learn all about them.