faq
Import order
Cannot access 'myAtom' before initialization
When sharing Atoms or Selectors that are locally declared in your component, it could be the case that you're trying to import an Atom from another file that has not actually initialized yet.
my-foo.js initializes an Atom and exports it:
import {LitElement, html} from 'lit-element';
import { LitAtom, atom } from '@klaxon/atom';
import './my-bar.js';
export const [myAtom, setMyAtom] = atom({
key: 'atom',
default: 1
});
class MyFoo extends LitAtom(LitElement) {/* etc*/}
my-bar.js then tries to import that Atom:
import {LitElement, html} from 'lit-element';
import { LitAtom, atom } from '@klaxon/atom';
import { myAtom } from './my-foo.js';
class MyBar extends LitAtom(LitElement) {/* etc*/}
What happens here, however, is that the import for my-bar.js occurs before the Atom has been exported, and will cause myAtom in my-bar.js to result in: Cannot access 'myAtom' before initialization. A simple fix for this is to just move the Atom to its own module, and import it in any other modules where it's needed.
Here's more simplified example of exactly what happens here:
file-a.js:
import './file-b.js';
export const foo = 'foo';
file-b.js:
import { foo } from './file-a.js';
console.log(foo); // Cannot access 'foo' before initialization
What happens in this case is that file-a.js imports file-b.js, where file-b imports foo. However, it tries to log foo to the console before it's initialized, because file-b.js is imported before foo is exported from file-a.js.
Conditionals in Selectors
When you use the getAtom or getSelector functions in your Selector, you essentially subscribe your Selector to those Atoms/Selectors. It could be the case that you have some conditional logic in your Selector:
const computedVal = selector({
key: 'computedVal',
get: ({getAtom}) => {
const valueA = getAtom(atomA);
/**
* If you return here, the Selector wont subscribe to updates from
* valueB until valueA has a value, and can lead to state "lagging" behind
*/
if (!valueA) return 0;
const valueB = getAtom(valueB);
return valueA + valueB;
}
});
You can still use conditionals in your Selectors, just make sure you move the condition under the Selectors dependencies:
const computedVal = selector({
key: 'computedVal',
get: ({getAtom}) => {
const valueA = getAtom(atomA);
const valueB = getAtom(valueB);
if (!valueA) return 0;
return valueA + valueB;
}
});
Update timing
The LitAtom Mixin will always make sure an Atom and all of its dependent Selectors have completed executing and updating their values before triggering a component rerender. Since LitElement batches updates asynchronously, and Selectors are async, this can cause multiple renders when you use multiple Selectors in a component. To mitigate this, the LitAtom Mixin schedules updates in a task after microtasks have run, and avoid multiple renders.
Consider the following example:
const [num, setNum] = atom({
key: 'num',
default: 1
});
const doubleNum = selector({
key: 'doubleNum',
get: ({getAtom}) => {
const number = getAtom(num);
return number * 2;
}
});
const doubleNumPlusOne = selector({
key: 'doubleNumPlusOne',
get: async ({getSelector}) => {
const doubleNumber = await getSelector(doubleNum);
return doubleNumber + 1;
}
});
class MyApp extends LitAtom(LitElement) {
static atoms = [num];
static selectors = [doubleNum, doubleNumPlusOne];
render() {
return html`
<button @click=${() => setNum(old => old + 1)}>increment</button>
<div>${this.num}</div>
<div>${this.doubleNum}</div>
<div>${this.doubleNumPlusOne}</div>
`
}
}
Clicking the button and updating the num Atom will only trigger one rerender for the component:
- user clicks button
numAtom is updated to2, notifies its dependencies:- the
MyAppcomponent - the
doubleNumSelector
- the
doubleNumSelector is executed, notifies its dependencies:- the
MyAppcomponent - the
doubleNumPlusOneSelector
- the
doubleNumPlusOneSelector is executed, notifies its dependencies:- the
MyAppcomponent
- the
- the
LitAtomMixin schedules a task to update the component, causing the component to rerender
Output:
[ATOM] num: 2
[SELECTOR] doubleNum: 4
[SELECTOR] doubleNumPlusOne: 5
[RERENDER]
Since Selectors run async, if we wouldnt have scheduled a task, this would have caused LitElements asynchronous render cycle to have run and completed 3 times; and rendered our component three times where we only needed one.
If you need to await when the component has ran all of its Atom/Selector updates (which can be useful in tests for example), you can use the litAtomUpdateComplete promise on the element instance:
await this.litAtomUpdateComplete;
If you want to opt-out of this behavior, you can override the scheduleUpdate method of the LitAtom Mixin, and instead call LitElements this.requestUpdate:
scheduleUpdate() {
this.requestUpdate();
}
⚠️ Be aware, however, that this is not advised and may cause multiple wasteful component renders.
If you're interested in reading more about LitElements asynchronous rendering, you can read more here.