pnpm add @but212/atom-effect
# or
npm install @but212/atom-effectimport { atom, computed, effect } from '@but212/atom-effect';
// 1. Create state (Atom)
const count = atom(0);
// 2. Derive state (Computed) - updates only when needed
const double = computed(() => count.value * 2);
// 3. React to changes (Effect) - runs immediately, then on changes
effect(() => {
console.log(`Count: ${count.value}, Double: ${double.value}`);
});
// Output: "Count: 0, Double: 0"
// 4. Update state
count.value++;
// Output: "Count: 1, Double: 2"- What: A mutable value wrapper.
- Why: Primitive values can't be observed. Atoms wrap them so the system can track readers.
- When: Use for your source of truth state.
- What: A value derived from atoms or other computeds.
- Why: It caches the result and only recalculates when inputs actually change.
- When: Use for derived data or transformations.
- What: A function that runs when observed data changes.
- Why: To bridge reactivity to the outside world (DOM updates, logging, network requests).
- When: Use for side effects. Avoid using to update other atoms (use
computedfor that).
Handling async data often requires cleaning up stale requests. effect supports a cleanup function.
const userId = atom(1);
effect(() => {
const currentId = userId.value;
const controller = new AbortController();
console.log(`Fetching user ${currentId}...`);
fetch(`/api/users/${currentId}`, { signal: controller.signal })
.then(r => r.json())
.then(data => console.log('User loaded:', data))
.catch(err => {
if (err.name !== 'AbortError') console.error(err);
});
// Cleanup function: runs before the next effect execution
return () => {
console.log(`Aborting fetch for user ${currentId}`);
controller.abort();
};
});
// Changing userId immediately aborts the previous fetch
userId.value = 2;By default, effects are batched via microtasks—multiple synchronous updates result in a single effect run. Use batch() when you need the effect to run synchronously after the updates.
import { atom, effect, batch } from '@but212/atom-effect';
const firstName = atom("John");
const lastName = atom("Doe");
effect(() => {
console.log(`Fullname: ${firstName.value} ${lastName.value}`);
});
// Without batch: effect runs on the next microtask (async)
firstName.value = "Jane";
lastName.value = "Smith";
// Effect has not run yet here
// With batch: effect runs immediately after batch ends (sync)
batch(() => {
firstName.value = "Alice";
lastName.value = "Brown";
});
// Effect has already run hereSometimes you need to read a value inside an effect without re-running the effect when that value changes.
const counter = atom(0);
const loggerEnabled = atom(true);
effect(() => {
// We depend on 'counter'
const val = counter.value;
// We read 'loggerEnabled' but don't want to re-run if ONLY the config changes
if (loggerEnabled.peek()) {
console.log("Logged:", val);
}
});- Architecture & Design: Internal design decisions.
- Contributing Guide: How to set up, test, and contribute.
- Changelog: Release notes.
| Package | Version | Description |
|---|---|---|
| @but212/atom-effect | Core reactive primitives (atom, computed, effect) |
|
| @but212/atom-effect-jquery | jQuery reactive bindings |
MIT © Jeongil Suk