useRefraction
useRefraction is the heart of state management in Refract. It creates reactive state that automatically updates your UI when the data changes - no manual subscriptions or complex state management needed.
Think of it as a supercharged version of React's useState, but with built-in reactivity that works seamlessly across your entire application.
Why useRefraction?
Traditional state management often requires you to manually connect components to state changes, manage subscriptions, and handle complex update patterns. Refract's useRefraction eliminates this complexity by making state inherently reactive.
When you change a refraction's value anywhere in your app, every component that uses that refraction automatically re-renders with the new data. It's like having a direct wire between your data and your UI.
lens.useRefraction
Since v1.0.0Creates or connects to a reactive state value within a component.
Signature
const refraction = lens.useRefraction(initialValue | existingRefraction)
Parameters
initialValueT | Refraction<T>requiredInitial value or existing refraction to connect to
Returns
Refraction object with .value property and .set() method
Example
// Create local refraction
const count = lens.useRefraction(0);
// Connect to global refraction
const theme = lens.useRefraction(globalTheme);
// Read value
console.log(count.value); // 0
// Update value
count.set(count.value + 1);
Comparison with Traditional Approaches
Let's see how useRefraction compares to traditional state management:
- React useState
- Refract useRefraction
- Live Result
import React, { useState } from 'react';
const Counter = () => {
const [count, setCount] = useState(0);
// Need to manually manage multiple state pieces
const increment = () => {
setCount(prev => prev + 1);
};
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
</div>
);
};
Issues with React useState:
- Multiple useState calls for related data
- Manual synchronization between state pieces
- Easy to forget to update related state
- No automatic reactivity across components
import { createComponent } from 'refract';
const Counter = createComponent(({ lens }) => {
const count = lens.useRefraction(0);
// Automatically reactive - no manual synchronization needed
const increment = () => {
count.set(count.value + 1);
// Message automatically updates!
};
return (
<div>
<p>Count: {count.value}</p>
<button onClick={increment}>Increment</button>
</div>
);
});
Benefits of Refract useRefraction:
- Single reactive state container
- Automatic computed values (message updates automatically)
- No manual synchronization needed
- Works seamlessly across components
- Built-in reactivity system
Count: 0
Basic Usage
import { createComponent } from "refract";
const SimpleCounter = createComponent(({ lens }) => {
// Create local reactive state
const count = lens.useRefraction(0);
const message = lens.useRefraction("Hello, Refract!");
return (
<div>
<h2>{message.value}</h2>
<p>Count: {count.value}</p>
<button onClick={() => count.set(count.value + 1)}>
Increment
</button>
<button onClick={() => count.set(0)}>
Reset
</button>
<button onClick={() => message.set("Updated message!")}>
Change Message
</button>
</div>
);
});
Working with Objects
const UserForm = createComponent(({ lens }) => {
// Object state
const user = lens.useRefraction({
name: "",
email: "",
age: 0,
preferences: {
theme: "light",
notifications: true
}
});
// Helper function for updating nested objects
const updateUser = (updates) => {
user.set({
...user.value,
...updates
});
};
const updatePreferences = (prefUpdates) => {
user.set({
...user.value,
preferences: {
...user.value.preferences,
...prefUpdates
}
});
};
return (
<form>
<input
type="text"
value={user.value.name}
onChange={(e) => updateUser({ name: e.target.value })}
placeholder="Name"
/>
<input
type="email"
value={user.value.email}
onChange={(e) => updateUser({ email: e.target.value })}
placeholder="Email"
/>
<input
type="number"
value={user.value.age}
onChange={(e) => updateUser({ age: parseInt(e.target.value) })}
placeholder="Age"
/>
<label>
<input
type="checkbox"
checked={user.value.preferences.notifications}
onChange={(e) => updatePreferences({ notifications: e.target.checked })}
/>
Enable notifications
</label>
<select
value={user.value.preferences.theme}
onChange={(e) => updatePreferences({ theme: e.target.value })}
>
<option value="light">Light</option>
<option value="dark">Dark</option>
</select>
<div className="user-preview">
<h3>Preview:</h3>
<pre>{JSON.stringify(user.value, null, 2)}</pre>
</div>
</form>
);
});
Working with Arrays
- Array State Management
const TodoList = createComponent(({ lens }) => {
const todos = lens.useRefraction([
{ id: 1, text: "Learn Refract", completed: false },
{ id: 2, text: "Build an app", completed: false }
]);
const newTodoText = lens.useRefraction("");
const addTodo = () => {
if (newTodoText.value.trim()) {
const newTodo = {
id: Date.now(),
text: newTodoText.value.trim(),
completed: false
};
// Always create new arrays for reactivity
todos.set([...todos.value, newTodo]);
newTodoText.set("");
}
};
const toggleTodo = (id) => {
todos.set(
todos.value.map(todo =>
todo.id === id
? { ...todo, completed: !todo.completed }
: todo
)
);
};
const deleteTodo = (id) => {
todos.set(todos.value.filter(todo => todo.id !== id));
};
const clearCompleted = () => {
todos.set(todos.value.filter(todo => !todo.completed));
};
return (
<div>
<div className="add-todo">
<input
type="text"
value={newTodoText.value}
onChange={(e) => newTodoText.set(e.target.value)}
onKeyDown={(e) => e.key === "Enter" && addTodo()}
placeholder="Add a new todo..."
/>
<button onClick={addTodo}>Add</button>
</div>
<div className="todo-list">
{todos.value.map(todo => (
<div key={todo.id} className={\`todo-item \${todo.completed ? 'completed' : ''}\`}>
<input
type="checkbox"
checked={todo.completed}
onChange={() => toggleTodo(todo.id)}
/>
<span className="todo-text">{todo.text}</span>
<button onClick={() => deleteTodo(todo.id)}>Delete</button>
</div>
))}
</div>
<div className="todo-actions">
<p>Total: {todos.value.length} | Completed: {todos.value.filter(t => t.completed).length}</p>
<button onClick={clearCompleted}>Clear Completed</button>
</div>
</div>
);
});
Global State Connection
Connect to global state using useRefraction:
- store.js
- Header.js
import { createRefraction } from "refract";
// Global refractions
export const globalTheme = createRefraction("light");
export const globalUser = createRefraction(null);
export const globalNotifications = createRefraction([]);
import { createComponent } from "refract";
import { globalTheme, globalUser } from "./store.js";
const Header = createComponent(({ lens }) => {
// Connect to global state
const theme = lens.useRefraction(globalTheme);
const user = lens.useRefraction(globalUser);
const toggleTheme = () => {
theme.set(theme.value === "light" ? "dark" : "light");
};
return (
<header className={\`header theme-\${theme.value}\`}>
<h1>My App</h1>
<div className="user-section">
{user.value ? (
<span>Welcome, {user.value.name}!</span>
) : (
<span>Please log in</span>
)}
</div>
<button onClick={toggleTheme}>
Switch to {theme.value === "light" ? "dark" : "light"} theme
</button>
</header>
);
});
export default Header;
When multiple components connect to the same global refraction, they all stay in sync automatically. Changes in one component immediately reflect in all others.
Related APIs
- useEffect - Reacting to state changes
- Refractions concept - Understanding reactive state
- State Management Best Practices - Best practices for managing state