Fluxo UIFluxo UIv0.4.1

Slices

Compose multiple independent state shards into a single store, or use any shard standalone. Slice subscribers and combined-store subscribers stay in sync automatically — no matter which side wrote the change.

Basic Slices

Basic Slices

Two slices (auth, cart) composed into one combined store. Writes from either side flow to the other automatically.

Auth Slice (useAuth)
renders: 1
User: Alice
Role: admin
Cart Slice (useCart)
renders: 1
Items: 0
Total: $0.00
Combined Store (useApp)
renders: 1
{
  "auth": {
    "user": "Alice",
    "role": "admin"
  },
  "cart": {
    "items": 0,
    "total": 0
  }
}
import { combineSlices, createHook, createSlice } from 'fluxo-ui/store';

const authSlice = createSlice<AuthState>('auth', () => ({ user: 'Alice', role: 'admin' }));
const cartSlice = createSlice<CartState>('cart', () => ({ items: 0, total: 0 }));

const appStore = combineSlices({ auth: authSlice, cart: cartSlice });

// Each slice has a hook of its own — selectors only see the slice's branch
const useAuth = createHook(authSlice);
const useCart = createHook(cartSlice);

// And the combined store has a hook for the whole tree
const useApp = createHook(appStore);

// Direct slice writes are reflected in the combined store and vice versa.
authSlice.setState({ role: 'editor' });           // appStore.getState().auth.role === 'editor'
appStore.setState((s) => ({ cart: { ...s.cart, items: s.cart.items + 1 } }));  // cartSlice subscribers fire

Standalone Slice

Standalone Slice

A slice used on its own — works exactly like a regular Store<T>. No combined parent required.

Standalone Timer Slice
renders: 1
0
paused
import { createHook, createSlice } from 'fluxo-ui/store';

// A slice is a fully-functional store on its own — no combineSlices needed.
const timerSlice = createSlice<TimerState>('timer', () => ({ seconds: 0, running: false }));
const useTimer = createHook(timerSlice);

// Direct framework-agnostic usage from plain JavaScript:
timerSlice.on('change', 'seconds', (s) => console.log('seconds:', s.seconds));
timerSlice.setState({ running: true });

// Later — adopt the same slice into a combined store with combineSlices({ timer: timerSlice, ... })
// The slice keeps the same identity; existing subscribers keep firing.

Bidirectional Sync

Bidirectional Sync

Slice subscribers and combined-store subscribers both fire on every change, no matter which side wrote.

Subscriber log (slice + combined fire on every write)
Click any button above to see both layers fire
import { combineSlices, createSlice } from 'fluxo-ui/store';

const counterSlice = createSlice<CounterState>('counter', () => ({ value: 0 }));
const settingsSlice = createSlice<SettingsState>('settings', () => ({ theme: 'light', debug: false }));

const syncStore = combineSlices({ counter: counterSlice, settings: settingsSlice });

// Slice-level path subscription — only fires for this slice's branch
counterSlice.on('change', 'value', (s) => console.log('[counter slice] value =', s.value));

// Combined-level path subscription — fully-qualified path including the slice key
syncStore.on('change', 'counter.value', (s) => console.log('[combined] counter.value =', s.counter.value));
syncStore.on('change', 'settings.theme', (s) => console.log('[combined] settings.theme =', s.settings.theme));

// Either write style triggers BOTH subscriptions:
counterSlice.setState({ value: 1 });             // slice direct
syncStore.setState((s) => ({ counter: { ...s.counter, value: 2 } })); // combined direct

Framework-Agnostic Core

Framework-Agnostic Core

The slicing API is plain TypeScript. It runs in Node, web workers, plain DOM apps, or any non-React framework. React is an optional thin wrapper via createHook().

Direct subscriber output (no React)
Listeners are registered with store.on() — same API as plain JS.
// Pure TypeScript / JavaScript — no React.
import { combineSlices, createSlice } from 'fluxo-ui/store';

const userSlice = createSlice('user', () => ({ name: 'Guest' }));
const flagsSlice = createSlice('flags', () => ({ darkMode: false }));

const store = combineSlices({ user: userSlice, flags: flagsSlice });

// Subscribe directly — these listeners fire in pure JS environments
// (Node, web workers, vanilla DOM) the same way they do in React.
store.on('change', 'user.name', (s) => {
    console.log('user.name =>', s.user.name);
});

userSlice.on('change', (s) => {
    console.log('user slice changed:', s);
});

// Writes from any side propagate everywhere.
userSlice.setState({ name: 'Alice' });
store.setState((s) => ({ flags: { ...s.flags, darkMode: true } }));

Import

import { createSlice, combineSlices, createHook, isSlice, isCombinedStore } from 'fluxo-ui/store';

API Reference

createSlice(name, initializer, middlewares?)
(name: string, initializer: () => T, middlewares?: Middleware<T>[]) => Slice<T>"-"

Create a named slice. Usable directly as a Store<T> in standalone mode, or adopted by combineSlices() for composition

combineSlices(slices, middlewares?)
(slices: Record<string, Slice>, middlewares?) => CombinedStore"-"

Compose multiple slices into a single store. Bidirectional sync: slice writes notify the combined store, combined writes notify the relevant slice's subscribers

slice.sliceName
string"-"

The name passed to createSlice. Used as the key in combined state

slice.getState() / setState() / on() / reset()
Same as Store<T>"-"

Slices implement the full Store<T> interface — works identically standalone or after being combined

combinedStore.slices
Record<string, Slice>"-"

The slices passed to combineSlices, exposed for direct slice access

isSlice(value) / isCombinedStore(value)
(value: unknown) => boolean"-"

Type guards for runtime checks

Features

Standalone-Capable Slices

A slice is a full Store<T> on its own. Use it directly — combine later without changing call sites.

Bidirectional Sync

Writes via the slice or the combined store both fan out to slice subscribers and combined-store subscribers.

Single State Cell

No duplicated state. Slice and combined store share one cloned state per batch — zero double-write or fan-out cost.

Path Subscriptions Across Layers

Slice-local paths and combined fully-qualified paths both work — listeners only fire for their relevant changes.

Microtask-Cached Reads

Slice.getState() caches the local branch within a microtask — repeated reads are O(1) without re-cloning the parent.

Framework-Agnostic

createSlice and combineSlices are pure TypeScript. React integration is optional via createHook(slice) or createHook(combined).