Overrides
Zedux's Dependency Injection (DI) model is extremely dynamic. This walkthrough has taught several ways to inject atoms and create the DI graph so far. But Zedux has another DI superpower: Overrides.
How to create atom overrides and use them to dynamically swap out atom implementations.
Creating
Atom templates have a .override()
method which will create an exact clone of the template, but with a different value.
const axiosAtom = atom('axios', axios)
const testAxiosAtom = axiosAtom.override({ post: jest.fn() })
You don't have to use .override()
. You can create a clone manually. The only requirement is that the override must have the same key
:
const exampleAtom = atom('example', 'some state')
const exampleAtomOverride = atom('example', 'overridden state!', { ttl: 0 })
The nice thing about .override()
for TS users is that it will tell you if the override doesn't match the overridden atom template's state, params, exports, or promise type.
Using
These overridden atoms can then be supplied to an ecosystem
via the overrides
ecosystem config option:
function TestApp() {
return (
<EcosystemProvider id="test" overrides={[testAxiosAtom]}>
<Routes />
</EcosystemProvider>
)
}
// or
const ecosystem = createEcosystem({
id: 'test',
overrides: [testAxiosAtom],
})
Now this test
ecosystem will use testAxiosAtom
everywhere axiosAtom
is used:
function Routes {
const axios = useAtomValue(axiosAtom) // testAxiosAtom is used instead
...
}
const userAtom = ion('user', ({ get }) => {
const axios = get(axiosAtom) // testAxiosAtom is injected instead
})
Live example:
Updating
Atom implementations can be swapped out dynamically using ecosystem.setOverrides()
. This is an extremely powerful feature of Zedux's DI model.
setOverrides
completely replaces the ecosystem's overrides with the list you pass. Use ecosystem.addOverrides()
and ecosystem.removeOverrides()
to selectively update only certain overrides.
Getting
Ecosystems have a .overrides
property which is an object mapping atom keys to their current override.
const myAtom = atom('theKey', () => 'the original')
const myAtomOverride = myAtom.override(() => 'the override')
const ecosystem = createEcosystem({ overrides: [myAtomOverride] })
ecosystem.overrides // { theKey: myAtomOverride }
Cool But Why?
The primary use case for DI overrides is probably to swap out atoms when testing. For example, if you create separate atoms to handle side effects like logging and network requests, it becomes trivial to swap out your loggingAtom
or axiosAtom
or socketAtom
during testing to inspect network requests triggered by tested atoms and feed mock data back to those atoms.
You can use DI overrides however you want. Here's a list of possibilities:
- Swap out side-effect atoms when testing.
- Swap out atoms in different environments - e.g. separate out atoms that use electron-only or mobile-only APIs and override those atoms when your app runs in the browser.
- Feature switches and a/b testing - e.g. put an effect in a
currentUserAtom
that swaps out atom implementations based on the current user's enabled features. - Theming - swap out a
themeAtom
based on user preferences.
This concept is so new in the React world that there may be amazing patterns yet to be discovered!
Recap
- Create an override of an atom with
myAtom.override()
or create a different atom with the same key. - Set an ecosystem's initial overrides with the
overrides
prop. - Update an ecosystem's overrides with
ecosystem.setOverrides()
,ecosystem.addOverrides()
, andecosystem.removeOverrides()
. - View the current overrides with
ecosystem.overrides
.
Next Steps
Remembering which atoms need to be overridden in which situations can get unruly in a big app. Let's look at some more ways to configure atoms that can help alleviate this.