Thanks to visit codestin.com
Credit goes to github.com

Skip to content

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

License

martyroque/nucleux

Repository files navigation

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

npm install nucleux

Quick Start

Create 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.

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.

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

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

2. useValue - Subscribe to specific atoms

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

3. useNucleux - Get everything at once

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

Advanced Features

Memoization

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

Persistence

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

Derived State

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

Store Dependencies

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

Custom Storage (React Native)

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

Debugging

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 Functionality

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

Individual Atom Reset

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

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

// 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:

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

Complete Example

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

Try It Live

View on CodeSandbox

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')
// 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.

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.

userStore = this.inject(UserStore);

this.watchAtom(atom, callback, immediate?)

Watch atom changes within the store.

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.

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)
// 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)
// 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.

await this.clearPersistedData();

this.resetValues()

Reset all atom values without affecting persisted data.

await this.resetValues();

React Hooks

useStore(StoreClass)

Get store instance with methods.

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

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

Subscribe to atom value.

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

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

useNucleux(StoreClass)

Get all methods and atom values.

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

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

Author

Marty Roque

License

ISC License

Copyright © 2025 Marty Roque.