Selectors
Selectors are the cornerstone of SoulState's design and performance. A selector is a pure function that receives the entire state and returns a specific slice of it.
SoulState is selector‑driven. Components subscribe to state changes through selectors, ensuring precise and efficient updates.
import { useStore } from 'soulstate/react';
import { counterStore } from './store';
// A simple selector extracting `count` from the full state
const countSelector = (state) => state.count;
function Counter() {
const count = useStore(counterStore, countSelector);
return <div>Count: {count}</div>;
}
Why Selectors Matter
Without selectors, a component would subscribe to the entire store, causing re-renders whenever any part of the state updates. Selectors allow focused subscriptions: a component re-renders only when the result of the selector changes.
Golden Rule: Select only the minimal state your component needs.
Selecting Multiple Values
❌ Anti-pattern: Returning a New Object
// Anti-pattern: new object every run → always re-renders
const data = useStore(counterStore, (state) => ({
count: state.count,
user: state.user,
}));
Because the selector returns a new object reference, SoulState's default equality check marks it as "changed" even if values stay identical.
✅ Solution: Use shallow
Use SoulState’s shallow comparison for multi-field selections.
import { shallow } from 'soulstate/utils';
const data = useStore(
counterStore,
(state) => ({
count: state.count,
user: state.user,
}),
shallow
);
Multiple Independent Selectors
For finer granularity and better subscription isolation, call useStore multiple times.
function UserProfile() {
const count = useStore(counterStore, (s) => s.count);
const user = useStore(counterStore, (s) => s.user);
const isActive = useStore(counterStore, (s) => s.isActive);
return (
<div>
<h1>{user.name}</h1>
<p>Count: {count}</p>
<p>Status: {isActive ? 'Active' : 'Inactive'}</p>
</div>
);
}
SoulState batches store notifications, so multiple selector calls do not produce extra emission overhead.
Derived State via Selectors
Selectors can compute derived data based solely on state.
function TodoStats() {
const total = useStore(todoStore, (s) => s.todos.length);
const completed = useStore(todoStore, (s) => s.todos.filter(t => t.completed).length);
const active = useStore(todoStore, (s) => s.todos.filter(t => !t.completed).length);
return (
<div>
<p>Total: {total}</p>
<p>Completed: {completed}</p>
<p>Active: {active}</p>
</div>
);
}
Selectors run whenever the store updates, so expensive selectors should be memoized.
Memoizing Expensive Selectors
SoulState does not memoize selector results internally. If your selector performs heavy work, memoize it using React or libraries like reselect.
Using useMemo
function ExpensiveList() {
const todos = useStore(todoStore, (s) => s.todos);
const filter = useStore(todoStore, (s) => s.filter);
const filtered = useMemo(() => {
return todos.filter(todo => {
if (filter === 'active') return !todo.completed;
if (filter === 'completed') return todo.completed;
return true;
});
}, [todos, filter]);
return <div>{filtered.length} items</div>;
}
Using reselect
import { createSelector } from 'reselect';
const selectTodos = (s) => s.todos;
const selectFilter = (s) => s.filter;
export const selectVisibleTodos = createSelector(
[selectTodos, selectFilter],
(todos, filter) => {
return todos.filter(todo => {
if (filter === 'active') return !todo.completed;
if (filter === 'completed') return todo.completed;
return true;
});
}
);
// const visible = useStore(todoStore, selectVisibleTodos);
Selector Best Practices
✅ Do
- Select the smallest possible slice of state
- Use multiple
useStorecalls for independent values - Use
shallowwhen returning objects - Memoize expensive computations
- Keep selectors pure (depend only on state)
❌ Don’t
- Return new objects without shallow comparison
- Select the entire state
- Insert side effects in selectors
- Base selector logic on external variables
Key Takeaway
Selectors give you surgical precision over component re-renders. When used correctly, they ensure optimal rendering performance and predictable state‑driven UI behavior in SoulState.