Migrating from React
HellaJS offers a simpler, more direct approach to building reactive UIs while maintaining many familiar concepts from React.
This guide will help you transition from React to HellaJS by showing equivalent patterns and highlighting key differences.
Key Differences
- Granular DOM Updates: HellaJS updates only the parts of the DOM that change, unlike React’s component-level updates
- Signals vs State: HellaJS signals automatically track dependencies and updates, compared to React’s manual state management
- Dedicated List Function: HellaJS optimizes list rendering using forEach, while React uses array.map()
- Element Lifecycle: HellaJS lifecycle hooks are tied to elements, not component instances like React
Component Rendering
React Virtual DOM
Uses a virtual DOM and re-renders components when state changes, then diffs the virtual DOM to update the real DOM.
function Counter() {
const [count, setCount] = useState(0);
return <div>{count}</div>; // Whole component re-executes on every render
}
HellaJS DOM Binding
Creates direct reactive bindings between signals and DOM elements, eliminating the need for diffing and re-renders.
const Counter = () => {
const count = signal(0);
return <div>{count}</div>; // Only this text node updates
};
Key Differences
- Direct Binding vs Virtual DOM: HellaJS binds signals directly to DOM elements, while React uses virtual DOM diffing
- Granular Updates vs Re-rendering: HellaJS updates only the specific DOM nodes that change, while React re-executes entire components
- Performance: HellaJS eliminates the overhead of virtual DOM creation and diffing that React requires
Component State
React useState
Manages local component state with setter functions that trigger re-renders.
function App() {
const [name, setName] = useState('World');
const [count, setCount] = useState(0);
return (
<div>
<h1>Hello, {name}!</h1>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>+</button>
<input value={name} onChange={e => setName(e.target.value)} />
</div>
);
}
HellaJS signal
Signals provide reactive state that automatically updates dependent DOM elements.
const App = () => {
const name = signal('World');
const count = signal(0);
return (
<div>
<h1>Hello, {name}!</h1>
<p>Count: {count}</p>
<button onClick={() => count(count() + 1)}>+</button>
<input value={name} onChange={e => name(e.target.value)} />
</div>
);
};
Key Differences
- API Pattern: HellaJS uses
signal(value)
function call pattern, while React uses[value, setter]
tuple - Reactivity: HellaJS updates specific DOM bindings, while React triggers component re-renders
- Dependency Tracking: HellaJS automatically tracks which elements depend on each signal, unlike React’s manual dependency management
Cached State
React useMemo
& useCallback
useMemo for expensive calculations and useCallback for function memoization with explicit dependency arrays.
function TodoApp() {
const [todos, setTodos] = useState([]);
const [filter, setFilter] = useState('all');
const filteredTodos = useMemo(() => {
return todos.filter(todo => {
if (filter === 'completed') return todo.done;
if (filter === 'active') return !todo.done;
return true;
});
}, [todos, filter]);
const addTodo = useCallback((text) => {
setTodos(prev => [...prev, { id: Date.now(), text, done: false }]);
}, []);
return (
<div>
<p>{filteredTodos.length} todos</p>
<button onClick={() => addTodo('New todo')}>Add</button>
</div>
);
}
HellaJS computed
Values automatically cache results and recompute only when their dependencies change.
const TodoApp = () => {
const todos = signal([]);
const filter = signal('all');
const filteredTodos = computed(() => {
return todos().filter(todo => {
if (filter() === 'completed') return todo.done;
if (filter() === 'active') return !todo.done;
return true;
});
});
const addTodo = (text) => {
todos([...todos(), { id: Date.now(), text, done: false }]);
};
return (
<div>
<p>{filteredTodos().length} todos</p>
<button onClick={() => addTodo('New todo')}>Add</button>
</div>
);
};
Key Differences
- Dependency Management: HellaJS automatically tracks dependencies, while React requires manual dependency arrays
- Caching Strategy: HellaJS uses lazy evaluation and automatic invalidation, while React memoizes based on dependency changes
- Function References: HellaJS functions are inherently stable, while React needs useCallback for stable references
Global State
React Context API
Context provides a way to pass data through the component tree without manually passing props at every level.
const ThemeContext = createContext();
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
}
function Button() {
const { theme, setTheme } = useContext(ThemeContext);
return (
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
Current theme: {theme}
</button>
);
}
HellaJS Global Signals
Global signals can be imported and used directly in any component without providers or context.
// Global signal - no provider needed
const theme = signal('light');
const Button = () => {
return (
<button onClick={() => theme(theme() === 'light' ? 'dark' : 'light')}>
Current theme: {theme}
</button>
);
};
// Any component can use the global signal directly
const App = () => {
return (
<div class={`app theme-${theme()}`}>
<Button />
</div>
);
};
HellaJS Stores
Fine-grained reactivity where each property is independently reactive.
// Store
const user = store({
name: '',
email: '',
preferences: {
theme: 'light'
}
});
// Component
const UserProfile = () => {
return (
<div>
<input
value={user.name}
onInput={e => user.name(e.target.value)}
/>
<p>Theme: {user.preferences.theme}</p>
<button onClick={() => user.preferences.theme('dark')}>
Dark Theme
</button>
</div>
);
};
Key Differences
- Direct Imports vs Provider Pattern: HellaJS uses direct imports, while React requires Provider components
- Global Access vs Prop Drilling: HellaJS signals work globally without setup, while React needs context to avoid prop drilling
- Fine-grained Updates: HellaJS stores update only components using specific properties, while React context updates all consumers
- Setup Simplicity: HellaJS works with simple imports, while React requires provider setup
Side Effects
React useEffect
useEffect manages side effects with manual dependency arrays and cleanup functions.
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
if (!userId) return;
setLoading(true);
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(data => {
setUser(data);
setLoading(false);
})
.catch(err => {
console.error('Failed to load user:', err);
setLoading(false);
});
}, [userId]); // Dependency array required
if (loading) return <div>Loading...</div>;
return <div>{user?.name}</div>;
}
HellaJS effect
Automatically tracks dependencies and handles async operations with built-in cancellation.
const UserProfile = ({ userId }) => {
const user = signal(null);
const loading = signal(false);
effect(async () => {
const id = userId(); // Automatically tracked
if (!id) return;
loading(true);
try {
const response = await fetch(`/api/users/${id}`);
const data = await response.json();
user(data);
} catch (err) {
console.error('Failed to load user:', err);
} finally {
loading(false);
}
});
return (
<div>
{loading() ? <div>Loading...</div> : <div>{user()?.name}</div>}
</div>
);
};
Key Differences
- Dependency Tracking: HellaJS automatically tracks signal reads, while React requires manual dependency arrays
- Async Handling: HellaJS effects support async/await directly, while React useEffect requires careful async patterns
- Cleanup: HellaJS provides automatic cleanup and cancellation, while React returns cleanup functions
- Race Conditions: HellaJS automatically handles cancellation of previous async operations, unlike React’s manual handling
Lists and Iteration
React Arrays
Render lists using array.map() with required key props for efficient reconciliation.
function TodoList() {
const [todos, setTodos] = useState([
{ id: 1, text: 'Learn React', done: false },
{ id: 2, text: 'Build app', done: true }
]);
const toggleTodo = (todo, checked) => {
setTodos(prev => prev.map(t =>
t.id === todo.id ? { ...t, done: checked } : t
));
};
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>
<input
type="checkbox"
checked={todo.done}
onChange={(e) => toggleTodo(todo, e.target.checked)}
/>
{todo.text}
</li>
))}
</ul>
);
}
HellaJS forEach
Optimized function for reactive list rendering with intelligent diffing.
const TodoList = () => {
const todos = signal([
{ id: 1, text: 'Learn HellaJS', done: false },
{ id: 2, text: 'Build app', done: true }
]);
const toggleTodo = (todo, checked) => {
todos(todos().map(t =>
t.id === todo.id ? { ...t, done: checked } : t
));
};
return (
<ul>
{forEach(todos, todo => (
<li key={todo.id}>
<input
type="checkbox"
checked={todo.done}
onChange={(e) => toggleTodo(todo, e.target.checked)}
/>
{todo.text}
</li>
))}
</ul>
);
};
Key Differences
- List Rendering: HellaJS uses dedicated forEach function for reactive lists, while React uses array.map()
- Performance: HellaJS uses advanced diffing algorithms (LIS) for minimal DOM operations, while React reconciles entire list
- Reactivity: HellaJS forEach provides granular updates, while React array changes trigger full re-render
- Reactive vs Static: HellaJS forEach maintains live reactive connections, while React .map() creates static snapshots
Lifecycle Hooks
React useEffect
Cleanup with useEffect and an empty dependency array that runs on mount, with cleanup function for unmount.
function Timer() {
const [count, setCount] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setCount(prev => prev + 1);
}, 1000);
// Cleanup on unmount
return () => clearInterval(interval);
}, []);
return <div>Timer: {count}</div>;
}
HellaJS onDestroy
Element-level lifecycle hooks that are tied directly to DOM elements rather than component instances.
const Timer = () => {
const count = signal(0);
const interval = setInterval(() => {
count(count() + 1);
}, 1000);
return (
<div onDestroy={() => clearInterval(interval)}>
Timer: {count}
</div>
);
};
Key Differences
- Lifecycle Scope: HellaJS hooks are element-scoped, while React hooks are component-scoped
- Cleanup Pattern: HellaJS uses onDestroy element attribute, while React uses useEffect cleanup returns
- Automatic Management: HellaJS automatically handles cleanup when elements are removed from DOM, unlike React’s manual cleanup
- Granular Control: HellaJS allows different lifecycle behavior per element within a component, while React manages at component level