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 separates createSignal() and createMemo()
  • Effect Patterns: HellaJS effect() automatically handles async operations, while SolidJS requires careful async handling in createEffect()
  • 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 uses createSignal() tuple pattern
  • Setter Pattern: HellaJS calls the signal directly count(newValue), while SolidJS uses separate setter setCount(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 uses createMemo()
  • 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 uses forEach() 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-scoped onDestroy
  • Cleanup Pattern: SolidJS uses onCleanup() within onMount(), while HellaJS uses onDestroy element attribute
  • Granular Control: HellaJS allows different cleanup behavior per element within a component