State Management Best Practices
Learn how to effectively manage reactive state in your Refract applications.
1. Immutable Updates
Always create new objects and arrays when updating complex state:
- ✅ Good - immutable updates
- ❌ Avoid - mutations
Immutable Update Patterns
const MyComponent = createComponent(({ lens }) => {
const state = lens.useRefraction({
user: { name: "John", age: 25 },
items: [1, 2, 3],
settings: { theme: "light" }
});
// ✅ Correct - creates new objects/arrays
const updateUserName = (newName) => {
state.set({
...state.value,
user: {
...state.value.user,
name: newName
}
});
};
const addItem = (item) => {
state.set({
...state.value,
items: [...state.value.items, item]
});
};
return <div>Component content</div>;
});
Avoid Mutations
const MyComponent = createComponent(({ lens }) => {
const state = lens.useRefraction({
user: { name: "John", age: 25 },
items: [1, 2, 3]
});
// ❌ Incorrect - mutates existing objects/arrays
const badUpdate = () => {
state.value.user.name = "Jane"; // Won't trigger updates!
state.value.items.push(4); // Won't trigger updates!
state.set(state.value);
};
return <div>Component content</div>;
});
2. Use Local State by Default
Start with local state and only move to global when needed:
- Local State
- Global State
Component-Specific State
// ✅ Good - local state for component-specific data
const Modal = createComponent(({ lens, children, onClose }) => {
const isAnimating = lens.useRefraction(false); // Only this modal needs this
return (
<div className={`modal ${isAnimating.value ? 'animating' : ''}`}>
{children}
</div>
);
});
Shared State
// ✅ Good - global state for shared data
const globalUser = createRefraction(null);
const UserProfile = createComponent(({ lens }) => {
const user = lens.useRefraction(globalUser); // Shared across app
return <div>Welcome, {user.value?.name}</div>;
});
3. Initialize with Appropriate Types
Use proper initial values that match your expected data structure:
- ✅ Good - proper initialization
- ❌ Avoid - unclear types
Proper Initialization
const MyComponent = createComponent(({ lens }) => {
// ✅ Good - proper initial values
const count = lens.useRefraction(0); // number
const message = lens.useRefraction(""); // string
const items = lens.useRefraction([]); // array
const user = lens.useRefraction(null); // nullable object
const settings = lens.useRefraction({ // object with defaults
theme: "light",
notifications: true
});
return <div>Component content</div>;
});
Avoid Unclear Types
const MyComponent = createComponent(({ lens }) => {
// ❌ Avoid - inconsistent types
const badState = lens.useRefraction(undefined); // unclear type
const badArray = lens.useRefraction(null); // should be []
return <div>Component content</div>;
});
4. State Organization Patterns
Flat State Structure
Keep state flat when possible for easier updates:
Flat State Structure
const UserForm = createComponent(({ lens }) => {
// ✅ Good - flat structure
const firstName = lens.useRefraction("");
const lastName = lens.useRefraction("");
const email = lens.useRefraction("");
const errors = lens.useRefraction({});
const handleSubmit = () => {
const userData = {
firstName: firstName.value,
lastName: lastName.value,
email: email.value
};
// Submit userData
};
return (
<form onSubmit={handleSubmit}>
<input
value={firstName.value}
onChange={(e) => firstName.set(e.target.value)}
/>
{/* More form fields */}
</form>
);
});
Grouped State for Related Data
Group related state when it makes sense:
Grouped State
const ShoppingCart = createComponent(({ lens }) => {
const cart = lens.useRefraction({
items: [],
total: 0,
discount: 0,
shipping: 0
});
const addItem = (item) => {
const newItems = [...cart.value.items, item];
const newTotal = calculateTotal(newItems);
cart.set({
...cart.value,
items: newItems,
total: newTotal
});
};
return <div>Cart content</div>;
});
5. Performance Considerations
Minimize Re-renders
Split state to minimize unnecessary re-renders:
Optimized State Splitting
const Dashboard = createComponent(({ lens }) => {
// Split frequently changing data
const notifications = lens.useRefraction([]);
const userProfile = lens.useRefraction(null);
const settings = lens.useRefraction({ theme: 'light' });
// This way, changing notifications doesn't re-render
// components that only depend on userProfile
return (
<div>
<UserProfile user={userProfile.value} />
<NotificationList notifications={notifications.value} />
<SettingsPanel settings={settings.value} />
</div>
);
});
Use Computed Values
Derive expensive calculations with computed values:
Computed Values
const ProductList = createComponent(({ lens, products }) => {
const searchQuery = lens.useRefraction("");
const sortBy = lens.useRefraction("name");
// ✅ Good - computed value for expensive operations
const filteredProducts = lens.useComputed(() => {
return products
.filter(p => p.name.toLowerCase().includes(searchQuery.value.toLowerCase()))
.sort((a, b) => a[sortBy.value].localeCompare(b[sortBy.value]));
}, [products, searchQuery.value, sortBy.value]);
return (
<div>
<input
value={searchQuery.value}
onChange={(e) => searchQuery.set(e.target.value)}
/>
{filteredProducts.value.map(product => (
<ProductCard key={product.id} product={product} />
))}
</div>
);
});
Related APIs
- useRefraction - Managing reactive state
- Refractions concept - Understanding reactive state