Migrating from SolidJS
HellaJS shares many conceptual similarities with SolidJS, both focusing on fine-grained reactivity and efficient DOM updates. This guide will help you transition by highlighting the key differences and showing equivalent patterns.
Key Differences
- Core Primitives: HellaJS uses
signal()
for both readable and writable state, while SolidJS separatescreateSignal()
andcreateMemo()
- Effect Patterns: HellaJS
effect()
automatically handles async operations, while SolidJS requires careful async handling increateEffect()
- Store Architecture: HellaJS stores provide automatic property reactivity, while SolidJS uses proxy-based stores with manual tracking
- List Rendering: HellaJS optimizes list rendering with
forEach()
, while SolidJS uses<For>
components
Component Rendering
SolidJS Fine-Grained Reactivity
SolidJS compiles JSX to fine-grained updates that directly bind to DOM nodes, avoiding virtual DOM overhead.
function Counter() {
const [count, setCount] = createSignal(0);
return <div>{count()}</div>; // Direct DOM binding
}
HellaJS DOM Binding
HellaJS also creates direct reactive bindings between signals and DOM elements with similar fine-grained updates.
const Counter = () => {
const count = signal(0);
return <div>{count}</div>; // Direct DOM binding
};
Key Differences
- Signal Creation: HellaJS uses unified
signal()
function, while SolidJS usescreateSignal()
tuple pattern - Setter Pattern: HellaJS calls the signal directly
count(newValue)
, while SolidJS uses separate settersetCount(newValue)
- Compilation: Both compile to fine-grained DOM updates, but with different internal representations
Component Communication
SolidJS Props and Signals
Props are reactive and components can pass signals directly.
function Parent() {
const [count, setCount] = createSignal(0);
return (
<div>
<Child count={count} onIncrement={() => setCount(c => c + 1)} />
</div>
);
}
function Child(props) {
return (
<div>
<p>Count: {props.count()}</p>
<button onClick={props.onIncrement}>Increment</button>
</div>
);
}
HellaJS Props and Signals
Similar reactive prop passing with HellaJS signals.
const Parent = () => {
const count = signal(0);
return (
<div>
<Child count={count} onIncrement={() => count(count() + 1)} />
</div>
);
};
const Child = ({ count, onIncrement }) => {
return (
<div>
<p>Count: {count}</p>
<button onClick={onIncrement}>Increment</button>
</div>
);
};
Key Differences
- Props Access: Both systems support reactive props, but HellaJS signals can be used directly in templates
- Signal Passing: Both allow passing signals as props for reactive component communication
- Event Handling: Similar patterns for callback props and event handling
Component State
SolidJS createSignal
Creates reactive state with getter/setter tuple pattern.
function App() {
const [name, setName] = createSignal('World');
const [count, setCount] = createSignal(0);
return (
<div>
<h1>Hello, {name()}!</h1>
<p>Count: {count()}</p>
<button onClick={() => setCount(count() + 1)}>+</button>
<input value={name()} onInput={e => setName(e.target.value)} />
</div>
);
}
HellaJS signal
Signals provide unified reactive state with single function interface.
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 single
signal(value)
function, while SolidJS uses[getter, setter]
tuple - Setter Interface: HellaJS calls signal with new value
signal(newValue)
, while SolidJS uses separate setter function - Template Binding: HellaJS signals can be used directly in templates, while SolidJS requires explicit getter calls
Cached State
SolidJS createMemo
Memoized computations that update when their dependencies change.
function TodoApp() {
const [todos, setTodos] = createSignal([]);
const [filter, setFilter] = createSignal('all');
const filteredTodos = createMemo(() => {
return todos().filter(todo => {
if (filter() === 'completed') return todo.done;
if (filter() === 'active') return !todo.done;
return true;
});
});
const addTodo = (text) => {
setTodos([...todos(), { 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
- API Naming: HellaJS uses
computed()
, while SolidJS usescreateMemo()
- Usage Pattern: Both systems provide lazy evaluation and automatic dependency tracking
- Caching Strategy: Both use similar memo caching with dependency invalidation
Global State
SolidJS Context API
Context provides reactive state sharing across component boundaries.
const ThemeContext = createContext();
function ThemeProvider(props) {
const [theme, setTheme] = createSignal('light');
const store = {
theme,
setTheme
};
return (
<ThemeContext.Provider value={store}>
{props.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 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>
);
};
SolidJS Stores
SolidJS provides createStore for nested reactive data structures.
const [user, setUser] = createStore({
name: '',
email: '',
preferences: {
theme: 'light'
}
});
const UserProfile = () => {
return (
<div>
<input
value={user.name}
onInput={e => setUser('name', e.target.value)}
/>
<p>Theme: {user.preferences.theme}</p>
<button onClick={() => setUser('preferences', 'theme', 'dark')}>
Dark 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 SolidJS supports both context and direct imports
- Store Updates: HellaJS uses signal-like property setters, while SolidJS uses path-based setters
- Nested Reactivity: Both provide fine-grained nested reactivity with different syntax approaches
Side Effects
SolidJS createEffect
createEffect manages side effects with automatic dependency tracking.
function UserProfile(props) {
const [user, setUser] = createSignal(null);
const [loading, setLoading] = createSignal(false);
createEffect(async () => {
const userId = props.userId;
if (!userId) return;
setLoading(true);
try {
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
setUser(data);
} catch (err) {
console.error('Failed to load user:', err);
} finally {
setLoading(false);
}
});
return (
<Show when={!loading()} fallback={<div>Loading...</div>}>
<div>{user()?.name}</div>
</Show>
);
}
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
- Async Support: Both support async effects, but HellaJS provides automatic cancellation of previous async operations
- Dependency Tracking: Both automatically track signal dependencies within effects
- Cleanup: Both provide cleanup mechanisms, with HellaJS handling async cancellation automatically
Lists and Iteration
SolidJS <For>
Component-based approach to efficient list rendering.
function TodoList() {
const [todos, setTodos] = createSignal([
{ id: 1, text: 'Learn SolidJS', 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>
<For each={todos()}>
{(todo) => (
<li>
<input
type="checkbox"
checked={todo.done}
onChange={(e) => toggleTodo(todo, e.target.checked)}
/>
{todo.text}
</li>
)}
</For>
</ul>
);
}
HellaJS forEach
Function-based approach with optimized reactive list rendering.
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
- Component vs Function: SolidJS uses
<For>
component, while HellaJS usesforEach()
function - Key Management: HellaJS requires explicit
key
prop, while SolidJS handles keying internally - Performance: Both use advanced diffing algorithms for efficient list updates
Lifecycle Hooks
SolidJS onMount
and onCleanup
Lifecycle hooks for component mount and cleanup operations.
function Timer() {
const [count, setCount] = createSignal(0);
onMount(() => {
const interval = setInterval(() => {
setCount(prev => prev + 1);
}, 1000);
onCleanup(() => clearInterval(interval));
});
return <div>Timer: {count()}</div>;
}
HellaJS onDestroy
Element-level lifecycle hooks tied directly to DOM elements.
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: SolidJS hooks are component-scoped with
onMount/onCleanup
, while HellaJS uses element-scopedonDestroy
- Cleanup Pattern: SolidJS uses
onCleanup()
withinonMount()
, while HellaJS usesonDestroy
element attribute - Granular Control: HellaJS allows different cleanup behavior per element within a component