Effects
1. Keep Effects Focused
Each effect should have a single, clear purpose:
- ✅ Good - focused effects
- ❌ Avoid - mixed concerns
const UserDashboard = createComponent(({ lens, userId }) => {
const user = lens.useRefraction(null);
const posts = lens.useRefraction([]);
const notifications = lens.useRefraction([]);
// ✅ Good - separate effects for separate concerns
// Effect for fetching user data
lens.useEffect(() => {
fetchUser(userId).then(user.set);
}, [userId]);
// Effect for fetching posts
lens.useEffect(() => {
if (user.value) {
fetchUserPosts(user.value.id).then(posts.set);
}
}, [user.value?.id]);
// Effect for document title
lens.useEffect(() => {
if (user.value) {
document.title = \`\${user.value.name} - Dashboard\`;
}
}, [user.value?.name]);
return <div>Dashboard content</div>;
});
Mixed Concerns
const UserDashboard = createComponent(({ lens, userId }) => {
const user = lens.useRefraction(null);
const posts = lens.useRefraction([]);
const notifications = lens.useRefraction([]);
lens.useEffect(() => {
fetchUser(userId).then(userData => {
user.set(userData);
document.title = userData.name;
fetchUserPosts(userData.id).then(posts.set);
});
}, [userId]);
return <div>Dashboard content</div>;
});
2. Minimize Dependencies
Only include values that should actually trigger the effect:
- ✅ Good - minimal dependencies
- ❌ Avoid - unnecessary dependencies
const SearchResults = createComponent(({ lens }) => {
const query = lens.useRefraction("");
const results = lens.useRefraction([]);
const loading = lens.useRefraction(false);
lens.useEffect(() => {
if (query.value.trim()) {
loading.set(true);
searchAPI(query.value)
.then(results.set)
.finally(() => loading.set(false));
} else {
results.set([]);
}
}, [query.value]);
const SearchResults = createComponent(({ lens }) => {
const query = lens.useRefraction("");
const results = lens.useRefraction([]);
const loading = lens.useRefraction(false);
lens.useEffect(() => {
if (query.value.trim()) {
loading.set(true);
searchAPI(query.value)
.then(results.set)
.finally(() => loading.set(false));
} else {
results.set([]);
}, [query.value, results.value, loading.value]);
3. Always Clean Up Resources
Prevent memory leaks by cleaning up subscriptions, timers, and listeners:
Clean Up Resources
const ResourceManager = createComponent(({ lens }) => {
const data = lens.useRefraction(null);
lens.useEffect(() => {
// Set up multiple resources
const timer = setInterval(() => {
console.log('Timer tick');
}, 1000);
const abortController = new AbortController();
const handleResize = () => {
console.log('Window resized');
};
// Set up resources
window.addEventListener('resize', handleResize);
fetch('/api/data', { signal: abortController.signal })
.then(response => response.json())
.then(data.set);
// ✅ Clean up ALL resources
return () => {
clearInterval(timer);
abortController.abort();
window.removeEventListener('resize', handleResize);
};
}, []);
return <div>Component with managed resources</div>;
});
4. Handle Async Operations Safely
Prevent race conditions and memory leaks with async operations:
Safe Async Operations
const AsyncDataComponent = createComponent(({ lens, dataId }) => {
const data = lens.useRefraction(null);
const loading = lens.useRefraction(false);
const error = lens.useRefraction(null);
lens.useEffect(() => {
let isCancelled = false;
const fetchData = async () => {
loading.set(true);
error.set(null);
try {
const response = await fetch(\`/api/data/\${dataId}\`);
const result = await response.json();
// ✅ Check if component is still mounted
if (!isCancelled) {
data.set(result);
loading.set(false);
}
} catch (err) {
if (!isCancelled) {
error.set(err.message);
loading.set(false);
}
}
};
fetchData();
// Cleanup function sets cancellation flag
return () => {
isCancelled = true;
};
}, [dataId]);
if (loading.value) return <div>Loading...</div>;
if (error.value) return <div>Error: {error.value}</div>;
return <div>{JSON.stringify(data.value)}</div>;
});