Skip to main content

Refractions

Refractions are Refract's way of handling reactive state—data that automatically updates your UI when it changes. Think of them as smart variables that know how to notify your components when their values change.

What Are Refractions?​

A refraction is like a container that holds a value. When you change what's inside the container, any component using that refraction automatically re-renders to reflect the new value. This happens seamlessly without you having to manually update the DOM.

import { useRefraction } from 'refract';

// Create a refraction with an initial value
const count = useRefraction(0);

// Read the current value
console.log(count.value); // 0

// Update the value - any components using this will re-render
count.set(5);
console.log(count.value); // 5

Creating Refractions​

There are several ways to create refractions depending on where you need them:

Local Refractions (Component-scoped)​

Most of the time, you'll create refractions inside components using the lens:

const Counter = createComponent(({ lens }) => {
// This refraction only exists within this component
const count = lens.useRefraction(0);

return (
<div>
<p>Count: {count.value}</p>
<button onClick={() => count.set(count.value + 1)}>
Increment
</button>
</div>
);
});

Global Refractions​

Sometimes you need state that multiple components can access—like user authentication status or app theme:

import { useRefraction } from 'refract';

// Created outside of any component - available everywhere
const currentUser = useRefraction(null);
const theme = useRefraction('light');

const Header = createComponent(({ lens }) => {
return (
<div className={`header ${theme.value}-theme`}>
{currentUser.value ? (
<span>Welcome, {currentUser.value.name}!</span>
) : (
<button onClick={() => showLoginModal()}>Login</button>
)}
</div>
);
});

const ThemeToggle = createComponent(({ lens }) => {
const toggleTheme = () => {
theme.set(theme.value === 'light' ? 'dark' : 'light');
};

return (
<button onClick={toggleTheme}>
Switch to {theme.value === 'light' ? 'dark' : 'light'} mode
</button>
);
});

Working with Different Data Types​

Refractions can hold any type of JavaScript value:

Simple Values​

const name = lens.useRefraction('John');
const age = lens.useRefraction(25);
const isVisible = lens.useRefraction(true);

Objects​

When working with objects, remember that you need to create a new object to trigger updates:

const user = lens.useRefraction({
name: 'Alice',
email: 'alice@example.com',
preferences: { theme: 'dark', notifications: true }
});

// ✅ Good - creates a new object
const updateEmail = (newEmail) => {
user.set({
...user.value,
email: newEmail
});
};

// ❌ Bad - mutates the existing object (won't trigger re-render)
const badUpdateEmail = (newEmail) => {
user.value.email = newEmail;
user.set(user.value); // This won't work as expected
};

Arrays​

Similar to objects, arrays need to be replaced with new arrays:

const todos = lens.useRefraction([]);

// ✅ Good - adding a new todo
const addTodo = (text) => {
todos.set([
...todos.value,
{ id: Date.now(), text, completed: false }
]);
};

// ✅ Good - removing a todo
const removeTodo = (id) => {
todos.set(todos.value.filter(todo => todo.id !== id));
};

// ✅ Good - updating a todo
const toggleTodo = (id) => {
todos.set(todos.value.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
));
};

Computed Refractions​

Sometimes you want a refraction whose value depends on other refractions. You can create computed refractions that automatically update when their dependencies change:

const firstName = lens.useRefraction('John');
const lastName = lens.useRefraction('Doe');

// This will automatically update when firstName or lastName changes
const fullName = lens.useComputed(() => {
return `${firstName.value} ${lastName.value}`;
}, [firstName, lastName]);

const UserCard = createComponent(({ lens }) => {
return (
<div>
<h3>{fullName.value}</h3> {/* Automatically shows "John Doe" */}
<input
value={firstName.value}
onChange={(e) => firstName.set(e.target.value)}
placeholder="First name"
/>
<input
value={lastName.value}
onChange={(e) => lastName.set(e.target.value)}
placeholder="Last name"
/>
</div>
);
});

Watching for Changes​

You can set up listeners that run when refractions change:

const shoppingCart = useRefraction([]);

// Watch for changes and save to localStorage
shoppingCart.watch((newCart) => {
localStorage.setItem('cart', JSON.stringify(newCart));
});

// Or inside a component, use effects
const CartPersistence = createComponent(({ lens }) => {
const cart = lens.useRefraction([]);

lens.useEffect(() => {
// Save to localStorage whenever cart changes
localStorage.setItem('cart', JSON.stringify(cart.value));
}, [cart.value]);

lens.useEffect(() => {
// Load from localStorage when component mounts
const savedCart = localStorage.getItem('cart');
if (savedCart) {
cart.set(JSON.parse(savedCart));
}
}, []); // Empty dependency array = runs once on mount

return <ShoppingCartDisplay items={cart.value} />;
});

Performance Tips​

Avoid Unnecessary Re-renders​

Only components that actually use a refraction will re-render when it changes:

const expensiveData = useRefraction(null);

const ExpensiveComponent = createComponent(({ lens }) => {
// This component won't re-render when expensiveData changes
// because it doesn't use expensiveData.value anywhere
return <div>I don't use expensive data</div>;
});

const DataDisplay = createComponent(({ lens }) => {
// Only this component re-renders when expensiveData changes
return <div>{expensiveData.value}</div>;
});

Batch Updates​

When you need to update multiple refractions at once, do it in a single function to avoid multiple re-renders:

const updateUserProfile = (newName, newEmail, newAge) => {
// Instead of three separate updates...
// name.set(newName);
// email.set(newEmail);
// age.set(newAge);

// Do this to batch the updates
lens.batch(() => {
name.set(newName);
email.set(newEmail);
age.set(newAge);
});
// Only one re-render happens after all updates are complete
};

Common Patterns​

Form Handling​

const ContactForm = createComponent(({ lens, onSubmit }) => {
const formData = lens.useRefraction({
name: '',
email: '',
message: ''
});

const updateField = (field, value) => {
formData.set({
...formData.value,
[field]: value
});
};

const handleSubmit = (e) => {
e.preventDefault();
onSubmit(formData.value);
// Reset form
formData.set({ name: '', email: '', message: '' });
};

return (
<form onSubmit={handleSubmit}>
<input
value={formData.value.name}
onChange={(e) => updateField('name', e.target.value)}
placeholder="Name"
/>
<input
value={formData.value.email}
onChange={(e) => updateField('email', e.target.value)}
placeholder="Email"
/>
<textarea
value={formData.value.message}
onChange={(e) => updateField('message', e.target.value)}
placeholder="Message"
/>
<button type="submit">Send</button>
</form>
);
});

Loading States​

const UserList = createComponent(({ lens }) => {
const users = lens.useRefraction([]);
const loading = lens.useRefraction(false);
const error = lens.useRefraction(null);

const fetchUsers = async () => {
loading.set(true);
error.set(null);

try {
const response = await fetch('/api/users');
const userData = await response.json();
users.set(userData);
} catch (err) {
error.set('Failed to load users');
} finally {
loading.set(false);
}
};

// Fetch users when component mounts
lens.useEffect(() => {
fetchUsers();
}, []);

if (loading.value) return <div>Loading users...</div>;
if (error.value) return <div>Error: {error.value}</div>;

return (
<ul>
{users.value.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
});

Debugging Refractions​

Refract provides helpful tools for debugging:

// Add a name to your refraction for easier debugging
const counter = lens.useRefraction(0, { name: 'counterValue' });

// Enable development mode to see refraction updates in console
const config = {
development: true,
logStateChanges: true
};

// You'll see logs like: "Refraction 'counterValue' changed from 0 to 1"

Next Steps​

Refractions handle your data, but to organize and reuse stateful logic across components, you'll want to learn about lensesthe system that provides controlled access to refractions and side effects within your components.