Observable Store
createStore()
createStore produces a minimal observable store. It needs no external library.
import { createStore } from '@objectifthunes/limestone-sdk';
const store = createStore({ count: 0, user: null as string | null });
The generic type is inferred from initialState. The state shape is fixed at creation time.
Reading and writing state
store.get()
Returns the current snapshot synchronously.
const state = store.get();
console.log(state.count); // 0
store.set(newState)
Merges partial state. Listeners are notified only when the reference changes.
store.set({ count: 1 });
// state is now { count: 1, user: null }
store.set(updater)
Accepts a function for updates that depend on the previous state.
store.set((prev) => ({ ...prev, count: prev.count + 1 }));
Subscribing to changes
store.subscribe registers a listener that fires after every state change. It returns an unsubscribe function.
const unsubscribe = store.subscribe(() => {
console.log('State changed:', store.get());
});
store.set({ count: 2 }); // listener fires
unsubscribe(); // stop listening
store.set({ count: 3 }); // listener does NOT fire
The listener receives no arguments. Call store.get() inside it to read the new state.
Selector-based subscriptions
Subscribe with a derived value to avoid re-renders from unrelated changes.
function subscribeToSelector<T, S>(
store: Store<T>,
selector: (state: T) => S,
listener: (value: S) => void,
): () => void {
let prev = selector(store.get());
return store.subscribe(() => {
const next = selector(store.get());
if (next !== prev) {
prev = next;
listener(next);
}
});
}
// Only fires when `count` changes, not when `user` changes
const unsub = subscribeToSelector(store, (s) => s.count, (count) => {
console.log('count is now', count);
});
useStore() hook
useStore binds a component to the store. It requires LimestoneProvider in the tree.
Full snapshot
import { useStore } from '@objectifthunes/limestone-sdk';
function Counter() {
const state = useStore();
return <Text>{state.count}</Text>;
}
The component re-renders on every state change.
Selector — granular re-renders
Pass a selector to subscribe only to a slice of state.
function Counter() {
const count = useStore((s) => s.count);
return <Text>{count}</Text>;
}
The component re-renders only when the selector result changes by strict equality (===). For objects or arrays, return a stable reference or use a primitive.
// Re-renders whenever any auth field changes
const authStatus = useStore((s) => s.auth.status);
// Re-renders only when the access token string changes
const token = useStore((s) => s.auth.tokens?.accessToken ?? null);
AppState shape
LimestoneProvider creates a Store<AppState>. The shape is:
interface AppState {
auth: AuthState;
network: NetworkState;
mutations: QueuedMutation[];
}
interface AuthState {
status: 'idle' | 'authenticating' | 'authenticated' | 'refreshing' | 'error';
user: Record<string, unknown> | null;
tokens: { accessToken: string; refreshToken: string } | null;
}
interface NetworkState {
connected: boolean;
type: 'wifi' | 'cellular' | 'none' | 'unknown';
}
Persistence with SecureStorageProvider
The store itself has no built-in persistence. Wire it manually using a SecureStorageProvider:
import { createStore } from '@objectifthunes/limestone-sdk';
import { createExpoSecureStore } from '@objectifthunes/limestone-sdk/expo-secure-store';
const storage = createExpoSecureStore();
const store = createStore({ theme: 'light', onboardingComplete: false });
// Rehydrate on boot
const saved = await storage.get('app-state');
if (saved) {
store.set(JSON.parse(saved));
}
// Persist on every change
store.subscribe(() => {
storage.set('app-state', JSON.stringify(store.get()));
});
Direct store access outside React
The store is available from useLimestone().store inside the component tree, or you can hold a reference to the store you created.
import { useLimestone } from '@objectifthunes/limestone-sdk';
function DebugPanel() {
const { store } = useLimestone();
const snapshot = store.get();
// ...
}
Outside React — for example in a background task — hold the store reference directly:
// store created during bootstrap
export const appStore = createStore({ /* ... */ });
// in a background task
const { auth } = appStore.get();
if (!auth.tokens) {
// redirect to login
}