Derivation in Anchor for React
Derivation in Anchor refers to the process of creating new reactive data or relationships from existing reactive states. This allows you to build complex data flows, computed values, and synchronized states that automatically update when their underlying dependencies change. Anchor provides several hooks in the React package to facilitate powerful derivation patterns.
How Derivation Works
Derivation in Anchor enables you to create computed values, synchronize states, and establish reactive relationships between different pieces of state. When a source state changes, any derived values or synchronized states automatically update, ensuring consistency throughout your application while maintaining optimal performance through fine-grained reactivity.
Primary Derivation APIs
These are the main APIs for deriving values from reactive states that cause re-renders.
useDerived(state, transform?)
Derives a computed value from a reactive state. The component re-renders automatically when the dependent state changes.
Params
state
- The reactive state to derive from.transform
(optional) - A function that receives the current value of thestate
and returns the computed value. If not provided, the hook returns thestate
itself, triggering re-renders on any change.recursive
(optional) - Whentransform
is not provided, this flag determines if changes in nested properties of thestate
should also trigger re-renders. Defaults tofalse
.
Usage
Basic Usage
import React from 'react';
import { useAnchor } from '@anchorlib/react';
import { useDerived } from '@anchorlib/react';
const UserProfile = () => {
const [user] = useAnchor({
firstName: 'John',
lastName: 'Doe',
age: 30,
});
// Derive a computed value
const fullName = useDerived(user, (currentUser) => {
return `${currentUser.firstName} ${currentUser.lastName}`;
});
const updateFirstName = () => {
user.firstName = 'Jane';
};
return (
<div>
<h1>Welcome, {fullName}!</h1>
<p>Age: {user.age}</p>
<button onClick={updateFirstName}>Change Name</button>
</div>
);
};
Without Transform Function
import React from 'react';
import { useAnchor } from '@anchorlib/react';
import { useDerived } from '@anchorlib/react';
const TodoList = () => {
const [todos] = useAnchor([
{ id: 1, text: 'Learn Anchor', completed: false },
{ id: 2, text: 'Build an app', completed: true },
]);
// Derive the state itself - re-renders on any change to the array
const observedTodos = useDerived(todos);
const addTodo = () => {
todos.push({
id: Date.now(),
text: 'New todo',
completed: false,
});
};
const toggleTodo = (id) => {
const todo = todos.find((t) => t.id === id);
if (todo) {
todo.completed = !todo.completed;
}
};
return (
<div>
<ul>
{observedTodos.map((todo) => (
<li key={todo.id} onClick={() => toggleTodo(todo.id)}>
<span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>{todo.text}</span>
</li>
))}
</ul>
<button onClick={addTodo}>Add Todo</button>
</div>
);
};
When to use it?
Use useDerived
when you need to create computed values from a single reactive state that automatically update when the source state changes. It's particularly useful for transforming or aggregating data from a reactive state.
useValue(state, key)
Derives a specific property's value from a reactive state. The component re-renders when that property changes.
Params
state
- The reactive state object.key
- The property key to derive.
Usage
Observing a Single Property
import React from 'react';
import { useAnchor } from '@anchorlib/react';
import { useValue } from '@anchorlib/react';
const UserDashboard = () => {
const [user] = useAnchor({
name: 'John Doe',
email: 'john@example.com',
age: 30,
theme: 'light',
});
// Only re-renders when user.name changes
const userName = useValue(user, 'name');
// Only re-renders when user.theme changes
const userTheme = useValue(user, 'theme');
const changeName = () => {
user.name = 'Jane Smith';
};
const toggleTheme = () => {
user.theme = user.theme === 'light' ? 'dark' : 'light';
};
return (
<div className={`theme-${userTheme}`}>
<h1>Welcome, {userName}!</h1>
<p>Email: {user.email}</p>
<p>Age: {user.age}</p>
<button onClick={changeName}>Change Name</button>
<button onClick={toggleTheme}>Toggle Theme</button>
</div>
);
};
When to use it?
Use useValue
when you need to observe a single property of a reactive state object. This provides fine-grained reactivity, ensuring that components only re-render when the specific property they depend on changes, not when any property in the object changes.
useValueIs(state, key, expect)
Checks if a specific property of a reactive state equals an expected value. The comparison re-evaluates when the property changes.
Params
state
- The reactive state object.key
- The property key to check.expect
- The expected value for comparison.
Usage
Conditional Rendering Based on State
import React from 'react';
import { useAnchor } from '@anchorlib/react';
import { useValueIs } from '@anchorlib/react';
const TaskManager = () => {
const [task] = useAnchor({
id: 1,
title: 'Complete project',
status: 'pending', // 'pending', 'in-progress', 'completed'
});
// Reactive boolean that updates when task.status changes
const isCompleted = useValueIs(task, 'status', 'completed');
const isInProgress = useValueIs(task, 'status', 'in-progress');
const startTask = () => {
task.status = 'in-progress';
};
const completeTask = () => {
task.status = 'completed';
};
return (
<div>
<h2>{task.title}</h2>
<p>Status: {task.status}</p>
{isCompleted && <p style={{ color: 'green' }}>✅ Task completed successfully!</p>}
{isInProgress && <p style={{ color: 'orange' }}>⏳ Task is in progress...</p>}
{!isCompleted && !isInProgress && <p style={{ color: 'gray' }}>⏸ Task is pending</p>}
{task.status === 'pending' && <button onClick={startTask}>Start Task</button>}
{task.status === 'in-progress' && <button onClick={completeTask}>Complete Task</button>}
</div>
);
};
When to use it?
Use useValueIs
when you need to conditionally render UI based on whether a specific property of a reactive state equals a particular value. This is especially useful for implementing UI states like selected items, active tabs, or status indicators.
State Synchronization APIs
These APIs allow you to synchronize states, creating reactive data flows between different pieces of state.
usePipe(source, target, transform?)
Establishes a unidirectional data flow, synchronizing changes from a source
reactive state to a target
reactive state. This is useful for scenarios where one state should always reflect a transformed version of another.
Params
source
- The reactive state from which changes will originate.target
- The reactive state to which changes will be applied.transform
(optional) - A function that receives thesource
state and returns a value to be applied to thetarget
state. If omitted, thesource
state itself will be assigned to thetarget
.
Usage
Mirroring User Input
import React from 'react';
import { useAnchor } from '@anchorlib/react';
import { usePipe } from '@anchorlib/react';
const InputMirror = () => {
const [inputState] = useAnchor({ text: '' });
const [displayState] = useAnchor({ mirroredText: '' });
// Pipe changes from inputState.text to displayState.mirroredText
usePipe(inputState, displayState, (source) => ({
mirroredText: source.text.toUpperCase(), // Transform the text to uppercase
}));
return (
<div>
<input
type="text"
value={inputState.text}
onChange={(e) => (inputState.text = e.target.value)}
placeholder="Type something..."
/>
<p>Mirrored (Uppercase): {displayState.mirroredText}</p>
</div>
);
};
Complex Data Transformation
import React from 'react';
import { useAnchor } from '@anchorlib/react';
import { usePipe } from '@anchorlib/react';
const DataProcessor = () => {
const [rawData] = useAnchor({
items: [
{ id: 1, name: 'Item 1', value: 10 },
{ id: 2, name: 'Item 2', value: 20 },
{ id: 3, name: 'Item 3', value: 30 },
],
});
const [processedData] = useAnchor({
summary: { total: 0, count: 0, average: 0 },
sortedItems: [],
});
// Process raw data into summary statistics
usePipe(rawData, processedData, (source) => {
const total = source.items.reduce((sum, item) => sum + item.value, 0);
const count = source.items.length;
const average = count > 0 ? total / count : 0;
return {
summary: { total, count, average },
sortedItems: [...source.items].sort((a, b) => b.value - a.value),
};
});
const addItem = () => {
rawData.items.push({
id: Date.now(),
name: `Item ${rawData.items.length + 1}`,
value: Math.floor(Math.random() * 100),
});
};
return (
<div>
<div>
<h3>Raw Data</h3>
<ul>
{rawData.items.map((item) => (
<li key={item.id}>
{item.name}: {item.value}
</li>
))}
</ul>
</div>
<div>
<h3>Processed Data</h3>
<p>Total: {processedData.summary.total}</p>
<p>Count: {processedData.summary.count}</p>
<p>Average: {processedData.summary.average.toFixed(2)}</p>
<h4>Sorted Items (by value)</h4>
<ul>
{processedData.sortedItems.map((item) => (
<li key={item.id}>
{item.name}: {item.value}
</li>
))}
</ul>
</div>
<button onClick={addItem}>Add Item</button>
</div>
);
};
When to use it?
Use usePipe
when you need to create a unidirectional data flow where changes in one state automatically update another state. This is particularly useful for data transformation, creating view models from domain models, or maintaining derived state that should always be consistent with a source state.
useBind(left, right, transformLeft?, transformRight?)
Creates a bidirectional synchronization between two reactive states. Changes in either the left
or right
state will automatically propagate to the other, with optional transformation functions for each direction.
Params
left
- The first reactive state to bind.right
- The second reactive state to bind.transformLeft
(optional) - A function to transform data when propagating fromleft
toright
.transformRight
(optional) - A function to transform data when propagating fromright
toleft
.
Usage
Synchronizing Form Fields
import React from 'react';
import { useAnchor } from '@anchorlib/react';
import { useBind } from '@anchorlib/react';
const BidirectionalSync = () => {
const [stateA] = useAnchor({ value: 'Hello' });
const [stateB] = useAnchor({ value: 'World' });
// Bind stateA.value and stateB.value
useBind(
stateA,
stateB,
(sourceA) => ({ value: sourceA.value.toUpperCase() }), // A to B: uppercase
(sourceB) => ({ value: sourceB.value.toLowerCase() }) // B to A: lowercase
);
return (
<div>
<div>
<label>State A (Uppercase to B): </label>
<input type="text" value={stateA.value} onChange={(e) => (stateA.value = e.target.value)} />
</div>
<div>
<label>State B (Lowercase to A): </label>
<input type="text" value={stateB.value} onChange={(e) => (stateB.value = e.target.value)} />
</div>
<p>State A Value: {stateA.value}</p>
<p>State B Value: {stateB.value}</p>
</div>
);
};
Unit Conversion
import React from 'react';
import { useAnchor } from '@anchorlib/react';
import { useBind } from '@anchorlib/react';
const UnitConverter = () => {
const [celsius] = useAnchor({ temperature: 0 });
const [fahrenheit] = useAnchor({ temperature: 32 });
// Bind celsius and fahrenheit with conversion functions
useBind(
celsius,
fahrenheit,
(celsiusState) => ({
temperature: (celsiusState.temperature * 9) / 5 + 32,
}),
(fahrenheitState) => ({
temperature: ((fahrenheitState.temperature - 32) * 5) / 9,
})
);
return (
<div>
<div>
<label>Celsius: </label>
<input
type="number"
value={celsius.temperature}
onChange={(e) => (celsius.temperature = Number(e.target.value))}
/>
</div>
<div>
<label>Fahrenheit: </label>
<input
type="number"
value={fahrenheit.temperature}
onChange={(e) => (fahrenheit.temperature = Number(e.target.value))}
/>
</div>
</div>
);
};
When to use it?
Use useBind
when you need bidirectional synchronization between two states, where changes in either state should automatically update the other. This is useful for unit conversions, form field synchronization, or maintaining consistency between different representations of the same data.
useFormWriter(state, keys)
Provides a comprehensive form management solution that automatically pipes valid form data back to a source state.
Params
state
- The reactive state object to which form data will be piped.keys
- An array of keys that represent the fields managed by this form.
Usage
Form Management
import React from 'react';
import { useAnchor } from '@anchorlib/react';
import { useFormWriter } from '@anchorlib/react';
const UserForm = () => {
const [user] = useAnchor({
name: '',
email: '',
age: 0,
});
// Create a form writer for user data
const form = useFormWriter(user, ['name', 'email', 'age']);
const handleSubmit = (e) => {
e.preventDefault();
if (form.isValid) {
// Form data is automatically piped to user state when valid
console.log('Form submitted:', user);
}
};
return (
<form onSubmit={handleSubmit}>
<div>
<label>Name:</label>
<input type="text" value={form.data.name} onChange={(e) => (form.data.name = e.target.value)} />
{form.errors.name && <span>{form.errors.name}</span>}
</div>
<div>
<label>Email:</label>
<input type="email" value={form.data.email} onChange={(e) => (form.data.email = e.target.value)} />
{form.errors.email && <span>{form.errors.email}</span>}
</div>
<div>
<label>Age:</label>
<input type="number" value={form.data.age} onChange={(e) => (form.data.age = Number(e.target.value))} />
{form.errors.age && <span>{form.errors.age}</span>}
</div>
<button type="submit" disabled={!form.isValid}>
Submit
</button>
<button type="button" onClick={form.reset}>
Reset
</button>
</form>
);
};
When to use it?
Use useFormWriter
when you need to manage form state with validation and automatic synchronization to a source state. It provides a complete solution for form handling including data management, validation, dirty state tracking, and resetting capabilities.
Advanced Data Flow APIs
These APIs provide more specialized data flow capabilities.
useAction(action)
Allows immediate reaction to value assignments, typically used with the ref pattern <div ref={action}>
to manipulate DOM elements (adding classes, getting bounding rectangles, adding event listeners, etc.) immediately as they are created, before they are painted to the browser. This avoids the noticeable repaint that occurs when making DOM changes inside effects, which run only after the browser has completely painted the DOM. It also manages side effects with automatic cleanup capabilities.
Params
init
(optional) - The initial value, can be null.action
- A function that takes the current value and returns a cleanup function.
Usage
DOM Element Manipulation
import React, { useState } from 'react';
import { useAction } from '@anchorlib/react';
const AnimatedElement = () => {
const [isVisible, setIsVisible] = useState(false);
// Use action to manipulate DOM element directly
const elementRef = useAction((element) => {
if (element) {
// Apply initial styles
element.style.transition = 'all 0.3s ease';
element.style.opacity = isVisible ? '1' : '0';
element.style.transform = isVisible ? 'translateY(0)' : 'translateY(20px)';
// Cleanup function
return () => {
// Cleanup code if needed
console.log('Element removed from DOM');
};
}
});
const toggleVisibility = () => {
setIsVisible(!isVisible);
};
return (
<div>
<button onClick={toggleVisibility}>{isVisible ? 'Hide' : 'Show'} Element</button>
{isVisible && (
<div
ref={elementRef}
style={{
width: '200px',
height: '100px',
backgroundColor: 'lightblue',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}>
Animated Element
</div>
)}
</div>
);
};
Event Listener Management
import React from 'react';
import { useAction } from '@anchorlib/react';
const ScrollDetector = () => {
const scrollRef = useAction((element) => {
if (element) {
const handleScroll = () => {
console.log('Element scrolled:', element.scrollTop);
};
element.addEventListener('scroll', handleScroll);
// Cleanup function to remove event listener
return () => {
element.removeEventListener('scroll', handleScroll);
};
}
});
return (
<div
ref={scrollRef}
style={{
width: '300px',
height: '200px',
overflow: 'auto',
border: '1px solid #ccc',
}}>
<div style={{ height: '500px', padding: '20px' }}>
<p>Scrollable content...</p>
<p>Scroll down to see more content.</p>
<p>Check the console for scroll events.</p>
<p>More content here...</p>
<p>Even more content...</p>
</div>
</div>
);
};
When to use it?
Use useAction
when you need to manipulate DOM elements directly, add event listeners, or perform other imperative operations on elements as they are created. It's particularly useful for animations, measurements, and event handling that need to happen before the browser paints the element.
Advanced Derivation APIs
These are more advanced APIs for specialized derivation scenarios.
useDerivedRef(state, handle)
Creates a mutable RefObject
that automatically updates its current
value based on changes in a reactive state. It also provides a handle
function that is called whenever the state changes or the ref's current
value is set, making it ideal for integrating with imperative APIs or external libraries that rely on refs.
Params
state
- The reactive state to derive from.handle
- A callback function that receives the currentstate
and thecurrent
value of the ref. This function is executed when thestate
changes or when the ref'scurrent
value is explicitly set.
Usage
Integrating with Third-Party Libraries
import React from 'react';
import { useAnchor } from '@anchorlib/react';
import { useDerivedRef } from '@anchorlib/react';
// Mock a third-party chart library function
const updateChart = (element, data) => {
if (element && data) {
// Simulate updating a chart with new data
console.log('Chart updated with data:', data);
element.textContent = `Chart showing: ${data.join(', ')}`;
}
};
const DataChart = () => {
const [chartData] = useAnchor({ values: [10, 50, 30, 80, 20] });
// useDerivedRef to update the chart when chartData.values changes
const chartRef = useDerivedRef(chartData, (currentData, element) => {
// This handle function is called when chartData.values changes
// or when chartRef.current is set.
updateChart(element, currentData.values);
});
const addRandomValue = () => {
chartData.values.push(Math.floor(Math.random() * 100) + 10);
};
return (
<div>
<div
ref={chartRef}
style={{
border: '1px solid black',
padding: '10px',
minHeight: '50px',
}}>
Chart will appear here
</div>
<button onClick={addRandomValue}>Add Random Value</button>
</div>
);
};
Working with DOM Elements
import React from 'react';
import { useAnchor } from '@anchorlib/react';
import { useDerivedRef } from '@anchorlib/react';
const ScrollingText = () => {
const [textState] = useAnchor({
content: 'Hello, World!',
speed: 1,
});
const textRef = useDerivedRef(textState, (currentState, element) => {
if (element) {
// Update text content when state changes
element.textContent = currentState.content;
// Apply speed-based styling
element.style.animationDuration = `${5 / currentState.speed}s`;
}
});
const updateText = () => {
textState.content = 'Text has been updated!';
};
const increaseSpeed = () => {
textState.speed += 0.5;
};
return (
<div>
<div
ref={textRef}
style={{
whiteSpace: 'nowrap',
overflow: 'hidden',
animation: 'scrolling 5s linear infinite',
}}>
{textState.content}
</div>
<button onClick={updateText}>Update Text</button>
<button onClick={increaseSpeed}>Increase Speed</button>
</div>
);
};
When to use it?
Use useDerivedRef
when you need to integrate reactive state with imperative APIs, third-party libraries, or DOM manipulation that requires direct access to elements. This hook bridges the gap between reactive state management and imperative programming patterns.
Best Practices
When working with derivation APIs, keep these best practices in mind:
- Choose the Right API: Use useValue for observing single properties, useDerived for computed values, and usePipe/useBind for state synchronization.
- Optimize for Performance: Use useValue instead of useDerived when you only need to observe a single property to ensure fine-grained reactivity.
- Keep Transformation Functions Pure: Transformation functions in useDerived, usePipe, and useBind should be pure functions without side effects to ensure predictable behavior.
- Use useDerivedRef for Imperative Integrations: When you need to work with APIs that require direct element access or imperative updates, useDerivedRef is the right tool for the job.
- Use useFormWriter for Form Management: For complex form handling with validation and automatic state synchronization, useFormWriter provides a comprehensive solution.
- Use useAction for Direct DOM Manipulation: When you need to manipulate DOM elements directly or add event listeners with automatic cleanup, useAction is the appropriate choice.