Reactivity API
Refract's reactivity system is what makes your UI come alive. It automatically keeps your components in sync with your state, without the need for manual updates or complex dependency tracking. Think of it like magic glasses - when the data changes, your UI automatically adjusts to show the new reality.
Meet the Players
useRefraction() - Your Reactive State Container
The bread and butter of Refract's reactivity. It creates a piece of state that automatically updates any components using it when changed.
import { useRefraction } from 'refract';
function Counter() {
const count = useRefraction(0);
return (
<button onClick={() => count.set(count.value + 1)}>
Clicks: {count.value}
</button>
);
}
Refractions work great for local component state. For shared state across components, consider using optics.
Key features:
- Automatic dependency tracking
- Simple API (
valueandset) - Works seamlessly with JSX
useOptic() - Reusable Reactive Logic
Optics let you package up reactive logic into reusable pieces. They're like supercharged hooks that automatically track their dependencies.
import { useOptic } from 'refract';
function useWindowSize() {
const size = useRefraction({ width: 0, height: 0 });
useOptic(() => {
const handler = () => size.set({
width: window.innerWidth,
height: window.innerHeight
});
window.addEventListener('resize', handler);
return () => window.removeEventListener('resize', handler);
}, []);
return size;
}
Always return a cleanup function from your optics to prevent memory leaks. Refract will automatically call it when needed.
How They Work Together
Refractions and optics form a powerful duo:
- Refractions store the state
- Optics manage how that state changes and reacts
Here's how they might work together in a real app:
function UserProfile() {
// Get reactive user data
const user = useOptic(useUserData);
// Local UI state
const isEditing = useRefraction(false);
return (
<div>
{isEditing.value ? (
<EditForm user={user} />
) : (
<ProfileDisplay user={user} />
)}
<button onClick={() => isEditing.set(!isEditing.value)}>
{isEditing.value ? 'Cancel' : 'Edit'}
</button>
</div>
);
}
Common Patterns
1. Derived State
Create computed values that automatically update:
const fullName = useMemo(() =>
`${user.value.firstName} ${user.value.lastName}`,
[user]
);
2. Async Operations
Handle asynchronous data with built-in cleanup:
useOptic(async () => {
const data = await fetch('/api/data');
if (!isCancelled) {
state.set(data);
}
}, []);
Always handle cancellation in async optics to prevent "set state after unmount" errors.
3. State Transformation
Create lenses to focus on specific data:
const emailLens = createLens(user, u => u.email);
Performance Considerations
While Refract's reactivity is highly optimized, here are some tips to keep things fast:
-
Memoize expensive computations
const filteredList = useMemo(
() => bigList.filter(expensiveFilter),
[bigList]
); -
Batch updates when possible
batch(() => {
user.set(newUser);
profile.set(newProfile);
}); -
Use traversals for bulk collection updates
Troubleshooting
My component isn't updating!
- Verify you're using
useRefractionoruseOptic - Check that you're using
.set()to update values - Ensure you haven't nested refractions unnecessarily
Memory leaks occurring
- All optics should return cleanup functions
- Cancel async operations on unmount
- Review our side effects guide
With these reactivity tools at your disposal, you're equipped to build dynamic, responsive interfaces that automatically stay in sync with your application state. The key is to start simple with useRefraction for local state, then graduate to useOptic as you need to share reactive logic across components. Remember - the more you leverage Refract's automatic dependency tracking, the less manual state management you'll need to handle.
For more advanced patterns and optimizations, explore how these reactivity primitives work with optical composition patterns and performance optimization techniques.