An open API service indexing awesome lists of open source software.

https://github.com/martyroque/nucleux

Simple atomic state management for React. No providers, no boilerplate.
https://github.com/martyroque/nucleux

atomic atomic-state hooks javascript open-source pubsub react react-hooks react-native state state-management store typescript typescript-library typescript-react

Last synced: about 1 month ago
JSON representation

Simple atomic state management for React. No providers, no boilerplate.

Awesome Lists containing this project

README

          




Simple, atomic hub for all your React application's state management needs.



Current npm package version.

---

## Why Nucleux?

- **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

## Installation

```bash
npm install nucleux
```

## Quick Start

Create a store with atomic state:

```javascript
import { Store } from 'nucleux';

class CounterStore extends Store {
count = this.atom(0);

increment() {
this.count.value += 1;
}
}
```

Use it in React components:

```javascript
import { useStore, useValue } from 'nucleux';

function Counter() {
const store = useStore(CounterStore);
const count = useValue(store.count);

return Count: {count};
}
```

That's it! No providers, no reducers, no dispatch.

## Core Concepts

### Atoms

Atoms are reactive pieces of state. When you change an atom's value, only components subscribed to that specific atom will re-render.

```javascript
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 },
];
}
}
```

### Three Ways to Use State

#### 1. `useStore` - Get store methods

```javascript
const todoStore = useStore(TodoStore);
// Access: todoStore.addTodo(), todoStore.toggleTodo(), etc.
```

#### 2. `useValue` - Subscribe to specific atoms

```javascript
const todos = useValue(todoStore.todos);
// Or directly: const todos = useValue(TodoStore, 'todos');
```

#### 3. `useNucleux` - Get everything at once

```javascript
const todo = useNucleux(TodoStore);
// Access: todo.todos, todo.filter, todo.addTodo(), etc.
```

## Advanced Features

### Memoization

Further improve atom updates and component re-renders:

```javascript
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:

```javascript
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' },
);
}
```

### Persistence

Save state automatically:

```javascript
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,
},
},
);
}
```

### Derived State

Compute values from multiple atoms:

```javascript
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;
},
);
}
```

### Store Dependencies

Inject other stores:

```javascript
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);
});
}
}
```

### Custom Storage (React Native)

Set storage for entire store:

```javascript
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:

```javascript
class AppStore extends Store {
settings = this.atom(
{ notifications: true },
{
persistence: {
persistKey: 'settings',
storage: AsyncStorage,
},
},
);
}
```

### Debugging

Track atom changes during development:

```javascript
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 Functionality

Reset atoms to their initial values and clear persisted data when needed.

#### Individual Atom Reset

```javascript
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 });
```

#### Store-Level Reset

```javascript
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
```

#### Common Use Cases

```javascript
// User logout - clear sensitive data
await userStore.reset({
atomKeys: ['profile', 'preferences'],
resetValues: true,
clearPersisted: true,
});

// App settings reset
await settingsStore.reset();
```

## React Native Setup

Install the polyfill and import it before Nucleux:

```bash
npm install react-native-get-random-values
```

```javascript
// App.js - Import this first!
import 'react-native-get-random-values';
import { useStore, useValue } from 'nucleux';
```

## Complete Example

```javascript
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 (



setInput(e.target.value)}
placeholder="Add todo..."
/>
Add


    {todos.map((todo) => (


  • toggleTodo(todo.id)}
    />
    {todo.text}


  • ))}


);
}
```

## Try It Live

[View on CodeSandbox](https://codesandbox.io/p/sandbox/nucleux-react-qw58s4)

## API Reference

### Store Methods

#### `this.atom(initialValue, options?)`

Create reactive state.

**Options:**

- `persistence` - Auto-save to storage
- `persistKey: string` - Storage key
- `storage?: SupportedStorage` - Custom storage (defaults to localStorage)
- `memoization` - Control when updates trigger
- `type: 'shallow' | 'deep' | 'custom'` - Comparison strategy
- `compare?: (a, b) => boolean` - Custom comparator (for `type: 'custom'`)

```javascript
// 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' },
},
);
```

#### `this.deriveAtom(atoms[], computeFn, memoization?)`

Create computed state that updates when source atoms change.

```javascript
filteredTodos = this.deriveAtom(
[this.todos, this.filter],
(todos, filter) => todos.filter((t) => t.status === filter),
{ type: 'deep' }, // Optional memoization
);
```

#### `this.inject(StoreClass)`

Inject another store as a dependency.

```javascript
userStore = this.inject(UserStore);
```

#### `this.watchAtom(atom, callback, immediate?)`

Watch atom changes within the store.

```javascript
constructor() {
super();
this.watchAtom(this.user, (newUser, prevUser) => {
console.log('User changed:', newUser);
});
}
```

#### `this.enableDebug()`

Enable console logging for all atom changes in the store.

```javascript
constructor() {
super();

// Enable debugging in development
if (process.env.NODE_ENV === 'development') {
this.enableDebug();
}
}
```

#### `atom.reset(options?)`

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)

```javascript
// Reset everything
await userAtom.reset();

// Reset value only
await userAtom.reset({ clearPersisted: false });

// Clear storage only
await userAtom.reset({ resetValue: false });
```

#### `this.reset(options?)`

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)

```javascript
// 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'],
});
```

#### `this.clearPersistedData()`

Clear all persisted data without affecting current values.

```javascript
await this.clearPersistedData();
```

#### `this.resetValues()`

Reset all atom values without affecting persisted data.

```javascript
await this.resetValues();
```

### React Hooks

#### `useStore(StoreClass)`

Get store instance with methods.

```javascript
const todoStore = useStore(TodoStore);
todoStore.addTodo('New task');
```

#### `useValue(atom)` or `useValue(StoreClass, 'atomKey')`

Subscribe to atom value.

```javascript
// Direct atom access
const todos = useValue(todoStore.todos);

// Store + key access
const todos = useValue(TodoStore, 'todos');
```

#### `useNucleux(StoreClass)`

Get all methods and atom values.

```javascript
const { todos, addTodo, removeTodo } = useNucleux(TodoStore);
```

---

**Requirements:** Node ≥14, React ≥16.9.0 (optional)

## Author

**Marty Roque**

- GitHub: [@martyroque](https://github.com/martyroque)
- X: [@lmproque](https://x.com/lmproque)
- LinkedIn: [@lmproque](https://www.linkedin.com/in/lmproque/)

## License

[ISC License](LICENSE)

Copyright © 2025 [Marty Roque](https://github.com/martyroque).