Sunday, November 16, 2025
Common QuantaJS Mistakes and How to Avoid Them
Posted by
Common QuantaJS Mistakes and How to Avoid Them
Every developer encounters pitfalls when learning a new library. Based on real-world usage and community feedback, we've compiled the most common mistakes developers make with QuantaJS and how to avoid them. This guide will save you hours of debugging and frustration!
1. Forgetting to Use this in Actions
❌ The Mistake
const store = createStore('counter', {
state: () => ({ count: 0 }),
actions: {
increment() {
count++; // ❌ Error: count is not defined
},
},
});
✅ The Solution
Always use this to access store properties in actions:
const store = createStore('counter', {
state: () => ({ count: 0 }),
actions: {
increment() {
this.count++; // ✅ Correct
},
},
});
Why This Happens
Actions are bound to the store instance, so you must use this to access state, getters, and other actions. This is similar to class methods in JavaScript.
2. Not Providing Store Name to useStore
❌ The Mistake
function Counter() {
const store = useStore(); // ❌ Error: Store name required
return <div>{store.count}</div>;
}
✅ The Solution
Always provide the store name:
function Counter() {
const store = useStore('counter'); // ✅ Correct
return <div>{store.count}</div>;
}
Alternative: Using Selectors
function Counter() {
const count = useStore('counter', store => store.count); // ✅ Even better
return <div>{count}</div>;
}
3. Incorrect QuantaProvider Setup
❌ The Mistake
function App() {
return (
<QuantaProvider store={counterStore}> {/* ❌ Wrong prop name */}
<Counter />
</QuantaProvider>
);
}
✅ The Solution
Use the stores prop (plural) with an object:
function App() {
return (
<QuantaProvider stores={{ counter: counterStore }}> {/* ✅ Correct */}
<Counter />
</QuantaProvider>
);
}
Multiple Stores
function App() {
return (
<QuantaProvider stores={{
user: userStore,
cart: cartStore,
settings: settingsStore
}}>
<App />
</QuantaProvider>
);
}
4. Mutating State Directly Instead of Using Actions
❌ The Mistake
// Direct mutation - works but breaks reactivity tracking
store.todos.push(newTodo); // ❌ May cause issues
store.user.name = 'New Name'; // ❌ May not trigger updates
✅ The Solution
Use actions for state mutations:
const store = createStore('todos', {
state: () => ({ todos: [] }),
actions: {
addTodo(todo) {
this.todos.push(todo); // ✅ Inside action, reactivity is tracked
},
updateUserName(name) {
this.user.name = name; // ✅ Properly tracked
},
},
});
// Use actions
store.addTodo(newTodo); // ✅ Correct
store.updateUserName('New Name'); // ✅ Correct
When Direct Mutation is OK
Direct mutation is fine for simple cases, but actions provide:
- Better debugging
- Consistent patterns
- Easier testing
- Clearer intent
5. Not Understanding Computed Values (Getters)
❌ The Mistake
// Expensive calculation in component
function TodoStats() {
const todos = useStore('todos', store => store.todos);
const completed = todos.filter(t => t.done).length; // ❌ Recalculates every render
return <div>Completed: {completed}</div>;
}
✅ The Solution
Use getters for computed values:
const todoStore = createStore('todos', {
state: () => ({ todos: [] }),
getters: {
completedCount: (state) => state.todos.filter(t => t.done).length, // ✅ Cached
activeCount: (state) => state.todos.filter(t => !t.done).length,
},
});
function TodoStats() {
const completed = useStore('todos', store => store.completedCount); // ✅ Uses cached value
return <div>Completed: {completed}</div>;
}
6. Watching Entire Stores Instead of Specific Values
❌ The Mistake
// Watches entire store - triggers on ANY change
watch(() => appStore, (store) => { // ❌ Too broad
console.log('Store changed');
});
✅ The Solution
Watch specific values:
// Watch specific property
watch(() => appStore.currentView, (view) => { // ✅ Specific
console.log('View changed:', view);
});
// Watch multiple specific values
watch(() => ({
view: appStore.currentView,
user: appStore.user?.id,
}), (values) => {
console.log('Relevant values changed:', values);
});
7. Not Cleaning Up Watchers
❌ The Mistake
useEffect(() => {
watch(() => store.value, (value) => {
console.log(value);
});
// ❌ No cleanup - watcher continues after unmount
}, []);
✅ The Solution
Always clean up watchers:
useEffect(() => {
const unwatch = watch(() => store.value, (value) => {
console.log(value);
});
return () => {
unwatch(); // ✅ Cleanup
};
}, []);
8. Incorrect Persistence Configuration
❌ The Mistake
// Persisting everything, including large objects
const store = createStore('app', {
state: () => ({
user: { /* large object */ },
cache: new Map(), // ❌ Can't serialize Map
temporaryData: [], // ❌ Don't need to persist
}),
persist: {
adapter: new LocalStorageAdapter('app'),
// ❌ Persists everything, including non-serializable data
},
});
✅ The Solution
Use selective persistence:
const store = createStore('app', {
state: () => ({
user: { /* large object */ },
cache: new Map(),
temporaryData: [],
}),
persist: {
adapter: new LocalStorageAdapter('app'),
include: ['user'], // ✅ Only persist what's needed
exclude: ['cache', 'temporaryData'], // ✅ Explicitly exclude
debounceMs: 300, // ✅ Debounce saves
},
});
9. Store Name Conflicts
❌ The Mistake
// Creating stores with the same name
const store1 = createStore('user', { /* ... */ });
const store2 = createStore('user', { /* ... */ }); // ❌ Error: Duplicate name
✅ The Solution
Use unique store names:
const userStore = createStore('user', { /* ... */ });
const userPreferencesStore = createStore('user-preferences', { /* ... */ }); // ✅ Unique
Best Practice: Use Descriptive Names
// ✅ Good naming
const shoppingCartStore = createStore('shopping-cart', { /* ... */ });
const userProfileStore = createStore('user-profile', { /* ... */ });
const appSettingsStore = createStore('app-settings', { /* ... */ });
10. Not Handling Async Actions Properly
❌ The Mistake
const store = createStore('user', {
state: () => ({ user: null, error: null }),
actions: {
async login(credentials) {
const user = await api.login(credentials); // ❌ No error handling
this.user = user; // ❌ No loading state
},
},
});
✅ The Solution
Handle async actions with proper error handling and loading states:
const store = createStore('user', {
state: () => ({
user: null,
error: null,
isLoading: false,
}),
actions: {
async login(credentials) {
this.isLoading = true;
this.error = null;
try {
const user = await api.login(credentials);
this.user = user;
} catch (error) {
this.error = error.message;
} finally {
this.isLoading = false;
}
},
},
});
11. Subscribing to Entire Store When Only Needing Specific Values
❌ The Mistake
function UserName() {
const store = useStore('user'); // ❌ Subscribes to entire store
return <div>{store.name}</div>; // Only needs name
}
✅ The Solution
Use selectors for fine-grained subscriptions:
function UserName() {
const name = useStore('user', store => store.name); // ✅ Only subscribes to name
return <div>{name}</div>;
}
Performance Impact
// ❌ Bad: Re-renders on ANY user store change
function UserProfile() {
const store = useStore('user');
return (
<div>
<div>Name: {store.name}</div>
<div>Email: {store.email}</div>
</div>
);
}
// ✅ Good: Only re-renders when name or email changes
function UserProfile() {
const name = useStore('user', store => store.name);
const email = useStore('user', store => store.email);
return (
<div>
<div>Name: {name}</div>
<div>Email: {email}</div>
</div>
);
}
12. Not Using TypeScript Types Correctly
❌ The Mistake
// No type safety
const store = createStore('user', {
state: () => ({ user: null }),
actions: {
login(user) {
this.user = user; // ❌ No type checking
},
},
});
✅ The Solution
Use TypeScript for type safety:
interface User {
id: string;
name: string;
email: string;
}
interface UserState {
user: User | null;
isLoading: boolean;
}
interface UserActions {
login(user: User): void;
logout(): void;
}
const userStore = createStore<UserState, {}, UserActions>('user', {
state: (): UserState => ({
user: null,
isLoading: false,
}),
actions: {
login(user: User) {
this.user = user; // ✅ Type-safe
},
logout() {
this.user = null;
},
},
});
13. Creating Stores Inside Components
❌ The Mistake
function Counter() {
// ❌ Creates new store on every render!
const store = createStore('counter', {
state: () => ({ count: 0 }),
});
return <div>{store.count}</div>;
}
✅ The Solution
Create stores outside components or use useCreateStore:
// ✅ Create outside component
const counterStore = createStore('counter', {
state: () => ({ count: 0 }),
});
function Counter() {
const store = useStore('counter');
return <div>{store.count}</div>;
}
// OR use useCreateStore for component-scoped stores
function Counter() {
const store = useCreateStore('counter', () => ({ count: 0 }));
return <div>{store.count}</div>;
}
14. Not Understanding Deep Watching Performance
❌ The Mistake
// Using deep watch unnecessarily
watch(() => store.settings, (settings) => {
// ...
}, { deep: true }); // ❌ Uses polling, can be slow
✅ The Solution
Use deep watching only when necessary, prefer specific property watching:
// ✅ Better: Watch specific properties
watch(() => store.settings.theme, (theme) => {
applyTheme(theme);
});
// Only use deep when you truly need it
watch(() => store.complexNestedObject, (obj) => {
// ...
}, { deep: true }); // ✅ Only when necessary
15. Forgetting to Reset Stores in Tests
❌ The Mistake
describe('Store Tests', () => {
const store = createStore('test', {
state: () => ({ count: 0 }),
});
it('test 1', () => {
store.count = 5;
expect(store.count).toBe(5);
});
it('test 2', () => {
// ❌ store.count is still 5 from previous test!
expect(store.count).toBe(0); // Fails!
});
});
✅ The Solution
Reset stores between tests:
describe('Store Tests', () => {
let store: ReturnType<typeof createStore>;
beforeEach(() => {
store = createStore('test', {
state: () => ({ count: 0 }),
});
});
it('test 1', () => {
store.count = 5;
expect(store.count).toBe(5);
});
it('test 2', () => {
store.$reset(); // ✅ Reset before test
expect(store.count).toBe(0); // ✅ Passes
});
});
16. Not Handling Store Destruction
❌ The Mistake
// Creating temporary stores without cleanup
function TemporaryComponent() {
const store = useCreateStore('temp', () => ({ data: [] }));
// ❌ Store persists even after component unmounts
return <div>{/* ... */}</div>;
}
✅ The Solution
Clean up stores when no longer needed:
function TemporaryComponent() {
const store = useCreateStore('temp', () => ({ data: [] }));
useEffect(() => {
return () => {
store.$destroy(); // ✅ Clean up on unmount
};
}, []);
return <div>{/* ... */}</div>;
}
17. Incorrect Selector Usage
❌ The Mistake
// Selector that creates new object every time
function Component() {
const data = useStore('store', store => ({
a: store.a,
b: store.b,
})); // ❌ New object every time = always re-renders
return <div>{/* ... */}</div>;
}
✅ The Solution
Use stable selectors or select individual values:
// ✅ Select individual values
function Component() {
const a = useStore('store', store => store.a);
const b = useStore('store', store => store.b);
return <div>{/* ... */}</div>;
}
// OR use a getter for computed objects
const store = createStore('store', {
state: () => ({ a: 1, b: 2 }),
getters: {
combined: (state) => ({ a: state.a, b: state.b }), // ✅ Cached
},
});
Quick Reference: Common Patterns
✅ Good Patterns
// 1. Use this in actions
actions: {
increment() { this.count++; }
}
// 2. Use selectors
const count = useStore('counter', store => store.count);
// 3. Use getters for computed values
getters: {
total: (state) => state.items.reduce((sum, item) => sum + item.price, 0)
}
// 4. Handle async properly
async action() {
this.loading = true;
try {
// ...
} finally {
this.loading = false;
}
}
// 5. Clean up watchers
useEffect(() => {
const unwatch = watch(/* ... */);
return () => unwatch();
}, []);
❌ Anti-Patterns to Avoid
// 1. Forgetting this
actions: { increment() { count++; } } // ❌
// 2. No store name
useStore() // ❌
// 3. Wrong provider prop
<QuantaProvider store={store} /> // ❌
// 4. Direct mutations everywhere
store.todos.push(item) // ❌ (use actions)
// 5. No cleanup
watch(/* ... */) // ❌ (no cleanup)
Debugging Tips
Enable Debug Logging
import { logger, LogLevel } from '@quantajs/core';
// In development
if (process.env.NODE_ENV === 'development') {
logger.configure({
level: LogLevel.DEBUG,
prefix: 'QuantaJS',
});
}
Check Store State
// Log store state
console.log('Store state:', store.state);
console.log('Store getters:', store.getters);
Verify Subscriptions
// Check if component is subscribed
const store = useStore('counter');
console.log('Subscribed to:', store);
Conclusion
Avoiding these common mistakes will make your QuantaJS development experience much smoother. Remember:
- Always use
thisin actions - Provide store names to
useStore - Use
storesprop (plural) for QuantaProvider - Use selectors for performance
- Handle async actions properly
- Clean up watchers and stores
- Use TypeScript for type safety
Most importantly: Read the error messages! QuantaJS provides helpful error messages that will guide you to the solution.
If you encounter issues not covered here, check our documentation or reach out to the community.
Happy coding! 🚀