Best Practices
Store Organization
Structure Your State Thoughtfully
Your store's state structure is crucial for maintainability and performance. Because Neutrix uses path-based updates, a flatter state often performs better and is easier to reason about:
typescript
import { createNeutrixStore } from 'neutrix';
// ❌ Avoid deeply nested state
const store = createNeutrixStore({
users: {
data: {
byId: {
settings: {
preferences: {}
}
}
}
}
});
// ✅ Use flatter state structures
const store2 = createNeutrixStore({
users: {},
userSettings: {},
userPreferences: {}
})
Computed Values
Use Computed Properties for Derived State
Instead of manually recalculating or storing derived data, leverage Neutrix's computed system:
typescript
import { createNeutrixStore } from 'neutrix';
// ❌ Avoid storing derived data
store.set('todoCount', store.get('todos').length);
store.set('activeTodoCount', store.get('todos').filter(t => !t.completed).length);
// ✅ Use computed values
const store = createNeutrixStore({ todos: [] });
store.store.computed('stats.todoCount', (state) => state.todos.length);
store.store.computed('stats.activeTodoCount', (state) =>
state.todos.filter(t => !t.completed).length
);
Batch Operations
Use Batch Updates for Multiple Changes
When you need to update multiple parts of your state at once, call the batch method:
typescript
// ❌ Avoid multiple individual updates
store.store.set('user.lastLogin', new Date());
store.store.set('user.loginCount', count + 1);
store.store.set('ui.lastAction', 'login');
// ✅ Use batch updates
store.store.batch([
['user.lastLogin', new Date()],
['user.loginCount', count + 1],
['ui.lastAction', 'login']
]);
Benefits:
- Atomic updates
- Single notification to subscribers
- Better performance
State Persistence
Be Selective About What You Persist
When using persistence, carefully choose what needs to be saved:
typescript
import { createNeutrixStore } from 'neutrix';
// ❌ Avoid persisting everything
const store = createNeutrixStore(initialState, {
persist: true
});
// ✅ Be selective about persistence
const selectiveStore = createNeutrixStore(initialState, {
persist: (state) => ({
user: state.user,
settings: state.settings
}),
name: 'app-store'
});
Always validate persisted state to ensure data integrity:
typescript
const validatedStore = createNeutrixStore(
{ count: 0, user: null },
{
persist: true,
validate: (state) => {
if (!state.user) return 'No user found in state';
return true;
}
}
);
Error Handling
Use Actions for Complex Operations
When dealing with async operations or complex state updates, use actions to handle errors properly:
typescript
// ❌ Raw async operations
async function updateUser(userId, data) {
store.store.set(`users.${userId}.loading`, true);
try {
const result = await api.updateUser(userId, data);
store.store.set(`users.${userId}`, result);
} catch (e) {
store.store.set(`users.${userId}.error`, e.message);
}
}
// ✅ Use actions for better error handling
const updateUserAction = store.store.action(
async (store, userId: string, data: any) => {
store.set(`users.${userId}.loading`, true);
try {
const result = await api.updateUser(userId, data);
store.set(`users.${userId}`, result);
return result;
} catch (error) {
store.set(`users.${userId}.error`, (error as Error).message);
throw error;
} finally {
store.set(`users.${userId}.loading`, false);
}
}
);
Middleware Usage
Use Middleware for Cross-Cutting Concerns
neutrix's middleware system is powerful but should be used carefully:
typescript
// ❌ Avoid putting business logic in middleware
const businessMiddleware: Middleware = {
onSet: (path, value) => {
if (path === 'user.age') {
return calculateAgeThings(value) // Business logic doesn't belong here
}
return value
}
}
// ✅ Use middleware for cross-cutting concerns
const loggingMiddleware: Middleware = {
onSet: (path, value, prevValue) => {
console.log(`${path} changed:`, prevValue, '→', value)
return value
}
}
const validationMiddleware: Middleware = {
onSet: (path, value) => {
if (value === undefined) {
throw new Error(`Cannot set ${path} to undefined`)
}
return value
}
}
Store Connections
Use Store Connections for Complex State Synchronization
When multiple stores need to remain in sync:
typescript
import { connectStore } from 'neutrix';
// ❌ Avoid manual store synchronization
storeA.store.subscribe(() => {
if (someCondition) {
storeB.store.set('something', storeA.store.get('something'));
}
});
// ✅ Use connectStore or connectStores
connectStore({
source: storeA.store,
target: storeB.store,
when: (source) => source.get('something') > 0,
then: (target) => {
target.set('something', 123);
}
});