
Security News
Socket Releases Free Certified Patches for Nuxt Security Vulnerabilities
Socket releases free Certified Patches for high-severity Nuxt vulnerabilities, including server-side remote code execution through server island props.
@mongez/atom
Advanced tools
An agnostic state management tool that work with any framework on browser or server
A powerful, framework-agnostic state management library that works seamlessly with any UI framework (React, Vue, Angular, Svelte) or Node.js application.
The main purpose of this package is to provide a simple, performant, and framework-agnostic state management solution that:
For React users, we have a dedicated package that works perfectly with React, it has some extra features that would fit with React components.
yarn add @mongez/atom
Or
npm i @mongez/atom
Or
pnpm add @mongez/atom
Get started in 30 seconds:
import { createAtom } from "@mongez/atom";
// 1. Create an atom
const counterAtom = createAtom({
key: "counter",
default: 0,
});
// 2. Read the value
console.log(counterAtom.value); // 0
// 3. Update the value
counterAtom.update(1);
// 4. Listen for changes
counterAtom.onChange((newValue, oldValue) => {
console.log(`Counter changed from ${oldValue} to ${newValue}`);
});
// 5. Update again to see the listener fire
counterAtom.update(2); // Logs: "Counter changed from 1 to 2"
That's it! You now understand 80% of what atoms do. Continue reading for advanced features.
Atoms use an event-driven architecture powered by @mongez/events. When an atom's value changes, it emits an event that all subscribers receive. This allows for:
All atoms are immutable by default. When you create an atom with an object or array as the default value, that original value is cloned and preserved:
const original = { name: "John" };
const userAtom = createAtom({
key: "user",
default: original,
});
userAtom.update({ name: "Jane" });
console.log(original); // { name: "John" } - unchanged!
console.log(userAtom.value); // { name: "Jane" }
Unlike Redux (React-focused) or Pinia (Vue-focused), atoms work everywhere:
This makes atoms perfect for:
Each atom manages one piece of state. This promotes:
Atoms are unique by their key. Each atom must have a unique key - attempting to create two atoms with the same key will throw an error.
import { createAtom, Atom } from "@mongez/atom";
// Simple value atom
export const currencyAtom = createAtom<string>({
key: "currency",
default: "EUR",
});
// Object atom
export type UserData = {
name: string;
email: string;
age: number;
id: number;
};
export const userAtom = createAtom<UserData>({
key: "user",
default: {
name: "Hasan",
age: 30,
email: "hassanzohdy@gmail.com",
id: 1,
},
});
// Array atom (use atomCollection for better array methods)
export const todosAtom = createAtom<string[]>({
key: "todos",
default: [],
});
TypeScript Tip: Always pass the value type as a generic for full type safety.
Access atom values using the .value property:
import { currencyAtom } from "~/atoms";
console.log(currencyAtom.value); // "EUR"
// For objects, you get the full object
console.log(userAtom.value); // { name: "Hasan", age: 30, ... }
// For nested values, use .get()
console.log(userAtom.get("name")); // "Hasan"
The basic way to update an atom is with .update():
// Direct value
currencyAtom.update("USD");
// Callback with old value
currencyAtom.update((oldValue, atom) => {
console.log(`Changing from ${oldValue}`);
return "GBP";
});
Important: For objects and arrays, you must pass a new reference to trigger change events:
// ❌ Won't trigger change event
userAtom.update(userAtom.value);
// ✅ Triggers change event
userAtom.update({ ...userAtom.value });
Subscribe to atom changes with .onChange():
const subscription = currencyAtom.onChange((newValue, oldValue, atom) => {
console.log(`Currency changed from ${oldValue} to ${newValue}`);
});
// Later, unsubscribe
subscription.unsubscribe();
The onChange method returns an EventSubscription with an unsubscribe method.
Reset an atom to its default value:
currencyAtom.update("USD");
console.log(currencyAtom.value); // "USD"
currencyAtom.reset();
console.log(currencyAtom.value); // "EUR" (default value)
Listen for reset events:
currencyAtom.onReset((atom) => {
console.log("Atom was reset!");
});
When an atom's default value is an object, it gets special treatment with additional methods.
Instead of spreading the old value manually, use .merge():
// Without merge
userAtom.update({
...userAtom.value,
name: "Ahmed",
age: 25,
});
// With merge (cleaner!)
userAtom.merge({
name: "Ahmed",
age: 25,
});
Update a single property with .change():
userAtom.change("name", "Ahmed");
userAtom.change("age", 25);
This is equivalent to:
userAtom.update({
...userAtom.value,
name: "Ahmed",
});
Use .get() to retrieve values from object atoms:
const userAtom = createAtom({
key: "user",
default: {
name: "Hasan",
address: {
city: "New York",
},
},
});
// Simple key
console.log(userAtom.get("name")); // "Hasan"
// Dot notation for nested values
console.log(userAtom.get("address.city")); // "New York"
// Default value if key doesn't exist
console.log(userAtom.get("email", "default@email.com")); // "default@email.com"
Watch for changes to specific properties with .watch():
const userAtom = createAtom({
key: "user",
default: {
name: "Hasan",
address: {
city: "New York",
},
},
});
// Watch a single property
userAtom.watch("name", (newName, oldName) => {
console.log(`Name changed from ${oldName} to ${newName}`);
});
// Watch nested property (dot notation)
userAtom.watch("address.city", (newCity, oldCity) => {
console.log(`City changed from ${oldCity} to ${newCity}`);
});
// Update triggers only relevant watchers
userAtom.update({
...userAtom.value,
name: "Ali", // Only name watcher fires
});
For arrays, use atomCollection instead of createAtom to get array-specific methods.
import { atomCollection } from "@mongez/atom";
const todoListAtom = atomCollection({
key: "todos",
default: [],
});
// Add to end
todoListAtom.push("Buy Milk");
todoListAtom.push("Buy Bread", "Buy Eggs"); // Multiple items
// Add to beginning
todoListAtom.unshift("Wake Up");
todoListAtom.unshift("Shower", "Breakfast"); // Multiple items
// Remove from end
todoListAtom.pop();
// Remove from beginning
todoListAtom.shift();
// Remove by index
todoListAtom.remove(0);
// Remove by callback
todoListAtom.remove((item) => item === "Buy Milk");
// Remove specific item (first occurrence)
todoListAtom.removeItem("Buy Milk");
// Remove all occurrences
todoListAtom.removeAll("Buy Milk");
// By index
console.log(todoListAtom.get(0)); // First item
// By callback
console.log(todoListAtom.get((item) => item.includes("Milk"))); // "Buy Milk"
// Find index
const index = todoListAtom.index((item) => item === "Buy Milk");
// Replace item at index
todoListAtom.replace(0, "Buy Bread");
// Map over items (updates the atom)
todoListAtom.map((item) => item.toUpperCase());
// Iterate (doesn't update)
todoListAtom.forEach((item, index) => {
console.log(`${index}: ${item}`);
});
console.log(todoListAtom.length); // Number of items
Extend atoms with custom methods using actions:
export const userAtom = createAtom({
key: "user",
default: {
name: "Hasan",
age: 30,
email: "",
},
actions: {
changeName(name: string) {
this.update({
...this.value,
name,
});
},
changeEmail(email: string) {
this.update({
...this.value,
email,
});
},
incrementAge() {
this.change("age", this.value.age + 1);
},
},
});
// Usage
userAtom.changeName("Ahmed");
userAtom.changeEmail("ahmed@example.com");
userAtom.incrementAge();
Type-Safe Actions:
type UserActions = {
changeName(name: string): void;
changeEmail(email: string): void;
incrementAge(): void;
};
export const userAtom = createAtom<UserData, UserActions>({
key: "user",
default: {
/* ... */
},
actions: {
/* ... */
},
});
All action methods are automatically bound to the atom instance, so this always refers to the atom.
Transform values before they're stored using beforeUpdate:
const multipleAtom = createAtom({
key: "multiple",
default: 0,
beforeUpdate(newNumber: number): number {
return newNumber * 2;
},
});
multipleAtom.update(4);
console.log(multipleAtom.value); // 8
This is useful for:
Customize how values are retrieved with a custom get function:
const settingsAtom = createAtom({
key: "settings",
default: {
isLoaded: false,
settings: {},
},
get(key: string, defaultValue: any = null, atomValue: any) {
// Check top-level first, then nested settings
return atomValue[key] !== undefined
? atomValue[key]
: atomValue.settings[key] !== undefined
? atomValue.settings[key]
: defaultValue;
},
});
settingsAtom.update({
isLoaded: true,
settings: {
websiteName: "My Website",
},
});
// Custom getter allows direct access
console.log(settingsAtom.get("websiteName")); // "My Website"
console.log(settingsAtom.get("isLoaded")); // true
Update without triggering change events:
// Silent update
currencyAtom.silentUpdate("USD"); // No onChange listeners fire
// Silent change (for objects)
userAtom.silentChange("name", "Ahmed"); // No onChange listeners fire
// Silent reset
currencyAtom.silentReset(); // Resets to default, fires onReset but not onChange
Use Cases:
Create a copy of an atom with a new key:
const clonedAtom = currencyAtom.clone();
console.log(clonedAtom.key); // "currencyCloned1234" (random suffix)
console.log(clonedAtom.value); // Same as original
Clones are independent - updating one doesn't affect the other.
| Function | Parameters | Returns | Description |
|---|---|---|---|
createAtom<Value, Actions>() | AtomOptions | Atom<Value, Actions> | Creates a new atom |
atomCollection<Value>() | CollectionOptions | Atom<Value[], AtomCollectionActions> | Creates an array atom with helper methods |
getAtom<T>(key) | key: string | Atom<T> | undefined | Gets an existing atom by key |
atomsList() | - | Atom<any>[] | Returns array of all atoms |
atomsObject() | - | Record<string, Atom> | Returns object of all atoms |
| Property | Type | Description |
|---|---|---|
key | string | Unique atom identifier |
value | Value | Current atom value (readonly) |
defaultValue | Value | Original default value (readonly) |
type | string | Type of value ("string", "number", "object", "array", etc.) |
length | number | Length of array/string values |
| Method | Parameters | Returns | Description |
|---|---|---|---|
update() | value | (oldValue, atom) => value | void | Updates atom value |
silentUpdate() | value | (oldValue, atom) => value | void | Updates without triggering onChange |
merge() | Partial<Value> | void | Merges object (objects only) |
change() | key, value | void | Updates single property (objects only) |
silentChange() | key, value | void | Updates property without triggering onChange |
reset() | - | void | Resets to default value |
silentReset() | - | void | Resets without triggering onChange |
| Method | Parameters | Returns | Description |
|---|---|---|---|
get() | key, defaultValue? | any | Gets value by key (objects only) |
| Method | Parameters | Returns | Description |
|---|---|---|---|
onChange() | (newValue, oldValue, atom) => void | EventSubscription | Listens for value changes |
watch() | key, (newValue, oldValue) => void | EventSubscription | Listens for property changes |
onReset() | (atom) => void | EventSubscription | Listens for reset events |
onDestroy() | (atom) => void | EventSubscription | Listens for destruction |
| Method | Parameters | Returns | Description |
|---|---|---|---|
clone() | - | Atom | Creates a copy with new key |
destroy() | - | void | Destroys atom and removes it |
| Method | Parameters | Returns | Description |
|---|---|---|---|
push() | ...items | void | Adds items to end |
unshift() | ...items | void | Adds items to beginning |
pop() | - | void | Removes last item |
shift() | - | void | Removes first item |
remove() | index | callback | void | Removes item |
removeItem() | item | void | Removes first occurrence |
removeAll() | item | Value[] | Removes all occurrences |
get() | index | callback | Value | undefined | Gets item |
index() | callback | number | Finds index |
map() | callback | Value[] | Maps and updates |
forEach() | callback | void | Iterates items |
replace() | index, item | void | Replaces item at index |
For React, use the dedicated @mongez/react-atom package which provides hooks:
import { atom } from "@mongez/react-atom";
const counterAtom = atom({
key: "counter",
default: 0,
});
function Counter() {
const count = counterAtom.useValue();
return (
<button onClick={() => counterAtom.update(count + 1)}>
Count: {count}
</button>
);
}
Use atoms with Vue's Composition API:
<script setup>
import { ref, onMounted, onUnmounted } from "vue";
import { createAtom } from "@mongez/atom";
const counterAtom = createAtom({
key: "counter",
default: 0,
});
const count = ref(counterAtom.value);
let subscription;
onMounted(() => {
subscription = counterAtom.onChange((newValue) => {
count.value = newValue;
});
});
onUnmounted(() => {
subscription.unsubscribe();
});
const increment = () => {
counterAtom.update(counterAtom.value + 1);
};
</script>
<template>
<button @click="increment">Count: {{ count }}</button>
</template>
Composable Pattern:
// useAtom.ts
import { ref, onMounted, onUnmounted } from "vue";
import type { Atom } from "@mongez/atom";
export function useAtom<T>(atom: Atom<T>) {
const value = ref(atom.value);
let subscription;
onMounted(() => {
subscription = atom.onChange((newValue) => {
value.value = newValue;
});
});
onUnmounted(() => {
subscription?.unsubscribe();
});
return value;
}
// Usage
import { useAtom } from "./useAtom";
const count = useAtom(counterAtom);
Integrate atoms with Angular services:
// counter.service.ts
import { Injectable, OnDestroy } from "@angular/core";
import { BehaviorSubject, Observable } from "rxjs";
import { createAtom } from "@mongez/atom";
import type { EventSubscription } from "@mongez/events";
@Injectable({
providedIn: "root",
})
export class CounterService implements OnDestroy {
private counterAtom = createAtom({
key: "counter",
default: 0,
});
private counterSubject = new BehaviorSubject(this.counterAtom.value);
public counter$: Observable<number> = this.counterSubject.asObservable();
private subscription: EventSubscription;
constructor() {
this.subscription = this.counterAtom.onChange((newValue) => {
this.counterSubject.next(newValue);
});
}
increment() {
this.counterAtom.update(this.counterAtom.value + 1);
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}
// counter.component.ts
import { Component } from "@angular/core";
import { CounterService } from "./counter.service";
@Component({
selector: "app-counter",
template: `
<button (click)="increment()">Count: {{ counter$ | async }}</button>
`,
})
export class CounterComponent {
counter$ = this.counterService.counter$;
constructor(private counterService: CounterService) {}
increment() {
this.counterService.increment();
}
}
Use atoms with Svelte stores:
// atomStore.ts
import { writable } from "svelte/store";
import type { Atom } from "@mongez/atom";
export function atomStore<T>(atom: Atom<T>) {
const { subscribe, set } = writable(atom.value);
atom.onChange((newValue) => {
set(newValue);
});
return {
subscribe,
update: (value: T) => atom.update(value),
};
}
<script>
import { createAtom } from '@mongez/atom';
import { atomStore } from './atomStore';
const counterAtom = createAtom({
key: 'counter',
default: 0,
});
const counter = atomStore(counterAtom);
function increment() {
counter.update($counter + 1);
}
</script>
<button on:click={increment}>
Count: {$counter}
</button>
Use atoms without any framework:
<!DOCTYPE html>
<html>
<body>
<button id="increment">Count: <span id="count">0</span></button>
<script type="module">
import { createAtom } from "@mongez/atom";
const counterAtom = createAtom({
key: "counter",
default: 0,
});
const countElement = document.getElementById("count");
const button = document.getElementById("increment");
// Update UI when atom changes
counterAtom.onChange((newValue) => {
countElement.textContent = newValue;
});
// Increment on click
button.addEventListener("click", () => {
counterAtom.update(counterAtom.value + 1);
});
</script>
</body>
</html>
Atoms work perfectly in Node.js for managing application state:
// userContext.ts
import { createAtom } from "@mongez/atom";
export function createUserContext(userId: string) {
return createAtom({
key: `user-${userId}`,
default: {
id: userId,
permissions: [],
preferences: {},
},
});
}
// middleware.ts
app.use((req, res, next) => {
req.userAtom = createUserContext(req.userId);
next();
});
// route.ts
app.get("/profile", (req, res) => {
const user = req.userAtom.value;
res.json(user);
});
import { createAtom } from "@mongez/atom";
const cacheAtom = createAtom<Record<string, any>>({
key: "cache",
default: {},
actions: {
set(key: string, value: any, ttl: number = 60000) {
this.change(key, value);
setTimeout(() => {
this.change(key, undefined);
}, ttl);
},
get(key: string) {
return this.value[key];
},
},
});
// Usage
cacheAtom.set("user:123", userData, 5000);
const cached = cacheAtom.get("user:123");
import { createAtom } from "@mongez/atom";
const configAtom = createAtom({
key: "config",
default: {
port: 3000,
database: {
host: "localhost",
port: 5432,
},
},
});
// Watch for config changes
configAtom.watch("database.host", (newHost) => {
console.log(`Database host changed to ${newHost}`);
// Reconnect to database
});
Create atoms that derive from other atoms:
const firstNameAtom = createAtom({
key: "firstName",
default: "John",
});
const lastNameAtom = createAtom({
key: "lastName",
default: "Doe",
});
const fullNameAtom = createAtom({
key: "fullName",
default: "",
});
// Update fullName when either name changes
const updateFullName = () => {
fullNameAtom.silentUpdate(`${firstNameAtom.value} ${lastNameAtom.value}`);
};
firstNameAtom.onChange(updateFullName);
lastNameAtom.onChange(updateFullName);
updateFullName(); // Initialize
Combine multiple atoms:
const themeAtom = createAtom({
key: "theme",
default: "light",
});
const languageAtom = createAtom({
key: "language",
default: "en",
});
const settingsAtom = createAtom({
key: "settings",
default: {},
actions: {
syncFromAtoms() {
this.merge({
theme: themeAtom.value,
language: languageAtom.value,
});
},
},
});
// Sync when either changes
themeAtom.onChange(() => settingsAtom.syncFromAtoms());
languageAtom.onChange(() => settingsAtom.syncFromAtoms());
Add middleware to atom updates:
const loggerMiddleware = (atom: Atom) => {
atom.onChange((newValue, oldValue) => {
console.log(`[${atom.key}] ${oldValue} → ${newValue}`);
});
};
const validationMiddleware = (
atom: Atom,
validator: (value: any) => boolean,
) => {
const originalUpdate = atom.update.bind(atom);
atom.update = (value: any) => {
const newValue =
typeof value === "function" ? value(atom.value, atom) : value;
if (validator(newValue)) {
originalUpdate(value);
} else {
console.error(`Validation failed for ${atom.key}`);
}
};
};
// Usage
const ageAtom = createAtom({ key: "age", default: 0 });
loggerMiddleware(ageAtom);
validationMiddleware(ageAtom, (age) => age >= 0 && age <= 120);
Persist atoms to localStorage:
function persistentAtom<T>(options: AtomOptions<T>) {
const storageKey = `atom:${options.key}`;
// Load from storage
const stored = localStorage.getItem(storageKey);
const defaultValue = stored ? JSON.parse(stored) : options.default;
const atom = createAtom({
...options,
default: defaultValue,
});
// Save on change
atom.onChange((newValue) => {
localStorage.setItem(storageKey, JSON.stringify(newValue));
});
return atom;
}
// Usage
const userAtom = persistentAtom({
key: "user",
default: { name: "", email: "" },
});
Handle async operations:
const userAtom = createAtom({
key: "user",
default: null,
actions: {
async fetchUser(userId: string) {
try {
const response = await fetch(`/api/users/${userId}`);
const user = await response.json();
this.update(user);
} catch (error) {
console.error("Failed to fetch user:", error);
}
},
},
});
// Usage
await userAtom.fetchUser("123");
✅ Use atoms for:
❌ Don't use atoms for:
1. Use .watch() for specific properties:
// ❌ Re-renders on ANY user change
userAtom.onChange(() => updateUI());
// ✅ Re-renders only on name change
userAtom.watch("name", () => updateUI());
2. Use silent updates for initialization:
// ❌ Triggers onChange during setup
userAtom.update(initialData);
// ✅ No onChange during setup
userAtom.silentUpdate(initialData);
3. Batch updates:
// ❌ Triggers onChange 3 times
userAtom.change("name", "John");
userAtom.change("age", 30);
userAtom.change("email", "john@example.com");
// ✅ Triggers onChange once
userAtom.merge({
name: "John",
age: 30,
email: "john@example.com",
});
Clean up subscriptions:
const subscription = atom.onChange(() => {});
// When done
subscription.unsubscribe();
Destroy unused atoms:
// When atom is no longer needed
atom.destroy();
Atoms are extremely fast:
TypeScript automatically infers types from default values:
const counterAtom = createAtom({
key: "counter",
default: 0, // Inferred as number
});
counterAtom.update(1); // ✅
counterAtom.update("1"); // ❌ Type error
For better type safety, always specify types explicitly:
type User = {
id: number;
name: string;
email: string;
};
const userAtom = createAtom<User>({
key: "user",
default: {
id: 0,
name: "",
email: "",
},
});
Create reusable atom factories:
function createEntityAtom<T extends { id: number }>(
key: string,
defaultValue: T,
) {
return createAtom<T>({
key,
default: defaultValue,
actions: {
updateById(id: number, updates: Partial<T>) {
if (this.value.id === id) {
this.merge(updates);
}
},
},
});
}
// Usage
const userAtom = createEntityAtom("user", {
id: 1,
name: "John",
});
Define action types for better autocomplete:
type UserActions = {
setName(name: string): void;
setEmail(email: string): void;
reset(): void;
};
const userAtom = createAtom<User, UserActions>({
key: "user",
default: { id: 0, name: "", email: "" },
actions: {
setName(name) {
this.change("name", name);
},
setEmail(email) {
this.change("email", email);
},
reset() {
this.reset();
},
},
});
// Full autocomplete and type checking
userAtom.setName("John"); // ✅
userAtom.setName(123); // ❌ Type error
Problem: Updating an atom but onChange doesn't fire.
Solution: Ensure you're passing a new reference for objects/arrays:
// ❌ Same reference
const user = userAtom.value;
user.name = "John";
userAtom.update(user); // Won't trigger onChange
// ✅ New reference
userAtom.update({ ...userAtom.value, name: "John" });
Problem: Error: Atom with key "user" already exists
Solution: Each atom must have a unique key. Either:
getAtom('user')?.destroy()Problem: Application slows down over time.
Solution: Always unsubscribe from onChange:
// ❌ Memory leak
atom.onChange(() => {});
// ✅ Proper cleanup
const subscription = atom.onChange(() => {});
// Later...
subscription.unsubscribe();
Problem: TypeScript doesn't recognize custom actions.
Solution: Define action types explicitly:
type MyActions = {
myAction(): void;
};
const atom = createAtom<MyType, MyActions>({
key: "myAtom",
default: {},
actions: {
myAction() {},
},
});
Redux:
// store.ts
const initialState = { count: 0 };
function counterReducer(state = initialState, action) {
switch (action.type) {
case "INCREMENT":
return { count: state.count + 1 };
default:
return state;
}
}
// component
const count = useSelector((state) => state.count);
dispatch({ type: "INCREMENT" });
Atoms:
// atoms.ts
const counterAtom = createAtom({
key: "counter",
default: { count: 0 },
actions: {
increment() {
this.change("count", this.value.count + 1);
},
},
});
// component (with @mongez/react-atom)
const count = counterAtom.use("count");
counterAtom.increment();
Benefits:
MobX:
class CounterStore {
@observable count = 0;
@action increment() {
this.count++;
}
}
const counter = new CounterStore();
Atoms:
const counterAtom = createAtom({
key: "counter",
default: { count: 0 },
actions: {
increment() {
this.change("count", this.value.count + 1);
},
},
});
Benefits:
Recoil:
const countState = atom({
key: "count",
default: 0,
});
const [count, setCount] = useRecoilState(countState);
Atoms:
const countAtom = createAtom({
key: "count",
default: 0,
});
const count = countAtom.useValue();
countAtom.update(newValue);
Benefits:
| Feature | Atoms | Redux |
|---|---|---|
| Boilerplate | Minimal | High |
| Learning Curve | Minutes | Hours |
| TypeScript | Excellent | Good |
| Framework | Agnostic | React-focused |
| Bundle Size | ~5KB | ~15KB+ |
| DevTools | Basic | Excellent |
Use Atoms when: You want simplicity and don't need time-travel debugging. Use Redux when: You need advanced DevTools and middleware ecosystem.
| Feature | Atoms | Zustand |
|---|---|---|
| API Style | Atom-based | Store-based |
| Framework | Agnostic | React-focused |
| Subscriptions | Fine-grained | Selector-based |
| TypeScript | Excellent | Excellent |
| Bundle Size | ~5KB | ~3KB |
Use Atoms when: You want framework-agnostic state or fine-grained subscriptions. Use Zustand when: You're React-only and want the smallest bundle.
| Feature | Atoms | Jotai |
|---|---|---|
| Atom Model | Mutable API | Immutable API |
| Framework | Agnostic | React-focused |
| Async | Manual | Built-in |
| TypeScript | Excellent | Excellent |
| Bundle Size | ~5KB | ~3KB |
Use Atoms when: You want to use atoms outside React. Use Jotai when: You're React-only and want atomic state with async support.
| Feature | Atoms | Recoil |
|---|---|---|
| Maturity | Stable | Experimental |
| Framework | Agnostic | React-only |
| API Complexity | Simple | Complex |
| Performance | Excellent | Excellent |
| Bundle Size | ~5KB | ~20KB |
Use Atoms when: You want a stable, simple solution. Use Recoil when: You need advanced features like atom families and selectors.
FAQs
An agnostic state management tool that work with any framework on browser or server
We found that @mongez/atom demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Socket releases free Certified Patches for high-severity Nuxt vulnerabilities, including server-side remote code execution through server island props.

Security News
An open letter signed by 50 companies, from NVIDIA and Microsoft to Mistral and Hugging Face, urges Washington not to restrict open weight AI.

Security News
/Research
A fake corepack.org site is impersonating the Node.js tool and delivers an infostealer and proxyware to developers who download it.