Simple, atomic hub for all your React application's state management needs.
- Zero boilerplate - Write less, do more
- No providers - Use state anywhere without wrapping components
- Atomic updates - Only subscribed components re-render
- Framework agnostic - Works with or without React
npm install nucleuxCreate a store with atomic state:
import { Store } from 'nucleux';
class CounterStore extends Store {
count = this.atom(0);
increment() {
this.count.value += 1;
}
}Use it in React components:
import { useStore, useValue } from 'nucleux';
function Counter() {
const store = useStore(CounterStore);
const count = useValue(store.count);
return <button onClick={store.increment}>Count: {count}</button>;
}That's it! No providers, no reducers, no dispatch.
Atoms are reactive pieces of state. When you change an atom's value, only components subscribed to that specific atom will re-render.
class TodoStore extends Store {
todos = this.atom([]);
filter = this.atom('all');
addTodo(text) {
this.todos.value = [
...this.todos.value,
{ id: Date.now(), text, done: false },
];
}
}const todoStore = useStore(TodoStore);
// Access: todoStore.addTodo(), todoStore.toggleTodo(), etc.const todos = useValue(todoStore.todos);
// Or directly: const todos = useValue(TodoStore, 'todos');const todo = useNucleux(TodoStore);
// Access: todo.todos, todo.filter, todo.addTodo(), etc.Further improve atom updates and component re-renders:
class AppStore extends Store {
// Shallow memoization (default) - compares by reference
count = this.atom(0);
// Deep memoization - compares object content
user = this.atom(
{ name: 'John', age: 30 },
{ memoization: { type: 'deep' } },
);
// Custom memoization - use your own comparison logic
product = this.atom(
{ name: 'Laptop', price: 999.99, discount: 10 },
{
memoization: {
type: 'custom',
compare: (a, b) => a.name === b.name && a.price === b.price,
},
},
);
}Works with derived atoms too:
class TodoStore extends Store {
todos = this.atom([]);
filter = this.atom('all');
// Further improve updates when filtered result content is the same
filteredTodos = this.deriveAtom(
[this.todos, this.filter],
(todos, filter) => todos.filter((t) => t.status === filter),
{ type: 'deep' },
);
}Save state automatically:
class UserStore extends Store {
// Simple persistence
theme = this.atom('dark', { persistence: { persistKey: 'theme' } });
// With custom storage
profile = this.atom(
{ name: '', email: '' },
{
persistence: {
persistKey: 'profile',
storage: AsyncStorage,
},
},
);
}Compute values from multiple atoms:
class TodoStore extends Store {
todos = this.atom([]);
filter = this.atom('all');
filteredTodos = this.deriveAtom(
[this.todos, this.filter],
(todos, filter) => {
if (filter === 'done') return todos.filter((t) => t.done);
if (filter === 'pending') return todos.filter((t) => !t.done);
return todos;
},
);
}Inject other stores:
class NotificationStore extends Store {
userStore = this.inject(UserStore);
notifications = this.atom([]);
constructor() {
super();
this.watchAtom(this.userStore.currentUser, (user) => {
if (user) this.loadNotifications(user.id);
});
}
}Set storage for entire store:
import AsyncStorage from '@react-native-async-storage/async-storage';
class AppStore extends Store {
storage = AsyncStorage;
settings = this.atom(
{ notifications: true },
{ persistence: { persistKey: 'settings' } },
);
}Or per atom:
class AppStore extends Store {
settings = this.atom(
{ notifications: true },
{
persistence: {
persistKey: 'settings',
storage: AsyncStorage,
},
},
);
}Track atom changes during development:
class TodoStore extends Store {
todos = this.atom([]);
filter = this.atom('all');
constructor() {
super();
// Enable debugging in development
if (process.env.NODE_ENV === 'development') {
this.enableDebug();
}
}
addTodo(text) {
this.todos.value = [...this.todos.value, { id: Date.now(), text }];
}
}Reset atoms to their initial values and clear persisted data when needed.
class UserStore extends Store {
theme = this.atom('light', { persistence: { persistKey: 'theme' } });
username = this.atom('guest');
}
const userStore = useStore(UserStore);
// Reset atom to initial value and clear storage
await userStore.theme.reset();
// Reset value but keep persisted data
await userStore.theme.reset({ clearPersisted: false });
// Clear persisted data but keep current value
await userStore.theme.reset({ resetValue: false });class AppStore extends Store {
theme = this.atom('light', { persistence: { persistKey: 'theme' } });
language = this.atom('en', { persistence: { persistKey: 'language' } });
isOnline = this.atom(true); // No persistence
}
const appStore = useStore(AppStore);
// Reset all atoms (values + persistence)
await appStore.reset();
// Reset specific atoms only
await appStore.reset({ atomKeys: ['theme', 'language'] });
// Convenience methods
await appStore.clearPersistedData(); // Clear persisted data only
await appStore.resetValues(); // Reset values only// User logout - clear sensitive data
await userStore.reset({
atomKeys: ['profile', 'preferences'],
resetValues: true,
clearPersisted: true,
});
// App settings reset
await settingsStore.reset();Install the polyfill and import it before Nucleux:
npm install react-native-get-random-values// App.js - Import this first!
import 'react-native-get-random-values';
import { useStore, useValue } from 'nucleux';import React from 'react';
import { Store, useNucleux } from 'nucleux';
class TodoStore extends Store {
todos = this.atom([]);
addTodo(text) {
const newTodo = { id: Date.now(), text, done: false };
this.todos.value = [...this.todos.value, newTodo];
}
toggleTodo(id) {
this.todos.value = this.todos.value.map((todo) =>
todo.id === id ? { ...todo, done: !todo.done } : todo,
);
}
}
function TodoApp() {
const { todos, addTodo, toggleTodo } = useNucleux(TodoStore);
const [input, setInput] = React.useState('');
const handleAdd = () => {
if (input.trim()) {
addTodo(input.trim());
setInput('');
}
};
return (
<div>
<div>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Add todo..."
/>
<button onClick={handleAdd}>Add</button>
</div>
<ul>
{todos.map((todo) => (
<li key={todo.id}>
<label>
<input
type="checkbox"
checked={todo.done}
onChange={() => toggleTodo(todo.id)}
/>
{todo.text}
</label>
</li>
))}
</ul>
</div>
);
}Create reactive state.
Options:
persistence- Auto-save to storagepersistKey: string- Storage keystorage?: SupportedStorage- Custom storage (defaults to localStorage)
memoization- Control when updates triggertype: 'shallow' | 'deep' | 'custom'- Comparison strategycompare?: (a, b) => boolean- Custom comparator (fortype: 'custom')
// Simple atom
count = this.atom(0);
// With persistence
theme = this.atom('dark', {
persistence: { persistKey: 'app-theme' },
});
// With memoization
user = this.atom(
{ name: 'John' },
{
memoization: { type: 'deep' },
},
);
// Combined options
profile = this.atom(
{ name: '', email: '' },
{
persistence: { persistKey: 'profile' },
memoization: { type: 'deep' },
},
);Create computed state that updates when source atoms change.
filteredTodos = this.deriveAtom(
[this.todos, this.filter],
(todos, filter) => todos.filter((t) => t.status === filter),
{ type: 'deep' }, // Optional memoization
);Inject another store as a dependency.
userStore = this.inject(UserStore);Watch atom changes within the store.
constructor() {
super();
this.watchAtom(this.user, (newUser, prevUser) => {
console.log('User changed:', newUser);
});
}Enable console logging for all atom changes in the store.
constructor() {
super();
// Enable debugging in development
if (process.env.NODE_ENV === 'development') {
this.enableDebug();
}
}Reset atom to initial value and/or clear persisted data.
Options:
resetValue?: boolean- Reset value to initial (default: true)clearPersisted?: boolean- Clear persisted data from storage (default: true)
// Reset everything
await userAtom.reset();
// Reset value only
await userAtom.reset({ clearPersisted: false });
// Clear storage only
await userAtom.reset({ resetValue: false });Reset multiple atoms in the store.
Options:
resetValues?: boolean- Reset values to initial (default: true)clearPersisted?: boolean- Clear persisted data (default: true)atomKeys?: string[]- Specific atoms to reset (default: all atoms)
// Reset all atoms
await this.reset();
// Reset specific atoms
await this.reset({ atomKeys: ['theme', 'language'] });
// Custom combination
await this.reset({
resetValues: false,
clearPersisted: true,
atomKeys: ['cache'],
});Clear all persisted data without affecting current values.
await this.clearPersistedData();Reset all atom values without affecting persisted data.
await this.resetValues();Get store instance with methods.
const todoStore = useStore(TodoStore);
todoStore.addTodo('New task');Subscribe to atom value.
// Direct atom access
const todos = useValue(todoStore.todos);
// Store + key access
const todos = useValue(TodoStore, 'todos');Get all methods and atom values.
const { todos, addTodo, removeTodo } = useNucleux(TodoStore);Requirements: Node ≥14, React ≥16.9.0 (optional)
Marty Roque
- GitHub: @martyroque
- X: @lmproque
- LinkedIn: @lmproque
Copyright © 2025 Marty Roque.