Anti-patterns
Common mistakes and anti-patterns to avoid when using Flexium.
State Management
❌ Reading State Outside Reactive Context
Problem: State read outside reactive context doesn't track updates.
// ❌ Anti-pattern
const [count] = state(0)
const displayCount = count // Only stores initial value, doesn't track updates
function Component() {
return <div>{displayCount}</div> // Always 0
}
// ✅ Correct approach
function Component() {
const [count, setCount] = state(0)
return <div>{count}</div> // Automatically tracks updates
}
// Or read inside effect
effect(() => {
console.log(count) // Tracks updates
})❌ Unnecessary Global State
Problem: Making state global when only used within a component causes memory waste and increased complexity.
// ❌ Anti-pattern
function Modal() {
// Only used in this component but made global
const [isOpen, setIsOpen] = state(false, { key: ['modal', 'open'] })
return isOpen ? <div>Modal</div> : null
}
// ✅ Correct approach
function Modal() {
// Local state is sufficient
const [isOpen, setIsOpen] = state(false)
return isOpen ? <div>Modal</div> : null
}When should you use Global State?
- When it needs to be shared across multiple components
- When it needs to be accessed app-wide (e.g., user authentication)
- For server data caching
❌ Global State Key Collision
Problem: Using meaningless or too generic keys can cause collisions.
// ❌ Anti-pattern
const [data, setData] = state(null, { key: ['data'] }) // Too generic
const [user, setUser] = state(null, { key: ['user'] }) // Collision possible
// ✅ Correct approach
// Use hierarchical keys
const [user, setUser] = state(null, { key: ['auth', 'user'] })
const [posts, setPosts] = state([], { key: ['user', userId, 'posts'] }) // Dynamic segments
// Or clear namespace
const [user, setUser] = state(null, { key: ['app', 'auth', 'user'] })
const [posts, setPosts] = state([], { key: ['app', 'user', userId, 'posts'] })Effects and Side Effects
❌ Updating State Inside Effect (Infinite Loop)
Problem: Updating tracked state inside effect can cause infinite loops.
// ❌ Anti-pattern - infinite loop
const [count, setCount] = state(0)
effect(() => {
setCount(count + 1) // count changes → effect re-runs → count changes → ...
})
// ✅ Correct approach 1: conditional update
effect(() => {
if (count < 10) {
setCount(count + 1)
}
})
// ✅ Correct approach 2: use untrack()
import { untrack } from 'flexium/core'
effect(() => {
untrack(() => {
setCount(count + 1) // Not tracked
})
})❌ Missing Cleanup Function
Problem: Not cleaning up timers, event listeners, subscriptions causes memory leaks.
// ❌ Anti-pattern - memory leak
const [isActive] = state(false)
effect(() => {
if (isActive) {
const interval = setInterval(() => {
console.log('tick')
}, 1000)
// No cleanup function → memory leak
}
})
// ✅ Correct approach
effect(() => {
if (isActive) {
const interval = setInterval(() => {
console.log('tick')
}, 1000)
return () => clearInterval(interval) // Cleanup required
}
})❌ Not Reading State Inside Effect
Problem: If state isn't read inside effect, dependencies aren't tracked.
// ❌ Anti-pattern - dependencies not tracked
const [count, setCount] = state(0)
effect(() => {
console.log('Hello') // Doesn't read count
})
setCount(1) // Effect doesn't re-run
// ✅ Correct approach
effect(() => {
console.log('Count:', count) // Must read count to track
})
setCount(1) // Effect re-runsComputed State
❌ Creating New Object/Array Every Time
Problem: If computed returns a new object every time, unnecessary recalculation occurs.
// ❌ Anti-pattern - new object every time
const [items] = state([1, 2, 3])
const [data] = state(() => ({
items: items,
timestamp: Date.now() // Different value each time → unnecessary recalculation
}), { deps: [items] })
// ✅ Correct approach - stable dependencies
const [items] = state([1, 2, 3])
const [timestamp] = state(() => Date.now(), { deps: [] }) // Manage as separate state
const [data] = state(() => ({
items: items,
timestamp: timestamp // Stable dependency
}), { deps: [items, timestamp] })❌ Side Effects Inside Computed
Problem: Computed should be pure functions. Handle side effects in effect.
// ❌ Anti-pattern - side effects
const [count, setCount] = state(0)
const [doubled] = state(() => {
console.log('Computing doubled') // Side effect
localStorage.setItem('count', String(count)) // Side effect
return count * 2
}, { deps: [count] })
// ✅ Correct approach
const [count, setCount] = state(0)
const [doubled] = state(() => count * 2, { deps: [count] }) // Pure function
// Side effects in effect
effect(() => {
console.log('Count changed:', count)
localStorage.setItem('count', String(count))
})Performance
❌ Not Batching Multiple State Updates
Problem: Updating multiple states consecutively causes re-renders for each.
// ❌ Anti-pattern - multiple re-renders
setA(1) // Re-render 1
setB(2) // Re-render 2
setC(3) // Re-render 3
// ✅ Correct approach - sync updates
import { sync } from 'flexium/core'
sync(() => {
setA(1)
setB(2)
setC(3)
}) // Single re-render❌ Unnecessary Computed Creation
Problem: Simple values don't need to be computed.
// ❌ Anti-pattern - unnecessary computed
const [count, setCount] = state(0)
const [displayCount] = state(() => count, { deps: [count] }) // Just returns value
// ✅ Correct approach
const [count, setCount] = state(0)
// Use count directly instead of displayCountWhen should you use Computed?
- When combining multiple states
- When complex calculation is needed
- When automatic memoization is needed
❌ Not Optimizing List Rendering
Problem: Regular map can cause performance issues with large lists.
// ❌ Anti-pattern - not optimized
{items.map(item => (
<Item key={item.id} data={item} />
))}
// ✅ Correct approach - use items.map()
{items.map((item) => <Item data={item} />)}Async State
❌ Missing Error Handling for Async State
Problem: Without checking error state, you can't provide feedback to users.
// ❌ Anti-pattern - no error handling
const [data, control] = state(async () => {
return fetch('/api/data').then(r => r.json())
})
function Component() {
return <div>{data}</div> // Shows nothing on error
}
// ✅ Correct approach
const [data, control] = state(async () => {
return fetch('/api/data').then(r => r.json())
})
function Component() {
if (control.loading) return <Spinner />
if (control.error) return <Error message={control.error.message} />
return <div>{data}</div>
}❌ Duplicate Requests for Async State
Problem: Calling multiple times with the same key causes duplicate requests.
// ❌ Anti-pattern - duplicate requests
function ComponentA() {
const [users, control] = state(async () => fetch('/api/users'), {
key: ['users']
})
return <div>...</div>
}
function ComponentB() {
// Same key but new request occurs
const [users, control] = state(async () => fetch('/api/users'), {
key: ['users']
})
return <div>...</div>
}
// ✅ Correct approach - same keys are automatically shared
// Both components share the same data, so only one request is madeType Safety
❌ Missing Type Specification
Problem: Type inference may fail with complex types.
// ❌ Anti-pattern - type inference may fail
const [user, setUser] = state(null)
setUser({ name: 'John' }) // Type error possible
// ✅ Correct approach - explicit type specification
interface User {
name: string
email: string
}
const [user, setUser] = state<User | null>(null)
setUser({ name: 'John', email: 'john@example.com' }) // Type safeCommon Mistakes
❌ Event Handler Case Confusion
Problem: Unlike React, Flexium uses lowercase event handlers.
// ❌ Anti-pattern - React style
<button onClick={handleClick}>Click</button>
<input onChange={handleChange} />
// ✅ Correct approach - lowercase
<button onclick={handleClick}>Click</button>
<input onchange={handleChange} />❌ Not Cleaning Up Global State
Problem: Not cleaning up unused global state causes memory leaks.
// ❌ Anti-pattern - no cleanup
function Component() {
const [data, control] = state(async () => fetch('/api/data'), {
key: ['temp', 'data']
})
// Remains in memory after component unmounts
}
// ✅ Correct approach - cleanup in effect
import { state, effect } from 'flexium/core'
function Component() {
const [data, control] = state(async () => fetch('/api/data'), {
key: ['temp', 'data']
})
effect(() => {
// Effect runs on mount
return () => {
// Manual cleanup if needed
}
})
}Summary
Things to Avoid
- ❌ Reading state outside reactive context
- ❌ Unnecessary Global State
- ❌ Updating state inside effect (infinite loop)
- ❌ Missing cleanup functions
- ❌ Creating new object/array every time
- ❌ Side effects inside computed
- ❌ Not batching updates
- ❌ Missing error handling
Recommendations
- ✅ Read state inside JSX or effect
- ✅ Don't use global if local is sufficient
- ✅ Sync updates with
sync() - ✅ Use
depsoption for derived state - ✅ Always return cleanup functions
- ✅ Specify types explicitly
- ✅ Check error states with
control.loadingandcontrol.error
Related Documentation
- FAQ - Frequently asked questions
- Best Practices - State Organization
- Best Practices - Performance Optimization
- Migration from React