Build a Counter App
Let’s build an interactive counter app to learn the fundamentals of HellaJS step-by-step.
What You’ll Learn
Core reactive concepts:
- Signals - How to create and use reactive state
- Derived Values - How to derive state automatically
- Effects - How to run side effects when state changes
Practical patterns:
- Event handling with reactive state updates
- Conditional rendering and styling based on state
Each section builds on the previous one, so you’ll see how these concepts work together to create reactive applications.
Project Setup
Installation
Use your favorite package manager to scaffold a new Vite project.
npm create vite@latest counter-app -- --template vanilla
Navigate into your new project directory and install the HellaJS packages, Vite plugin, and Tailwind CSS.
cd counter-app
npm install @hellajs/core @hellajs/dom
npm install -D vite-plugin-hellajs @tailwindcss/vite@next
Configuration
Update your vite.config.js
to use the HellaJS plugin and Tailwind CSS.
// vite.config.js
import { defineConfig } from 'vite';
import viteHellaJS from 'vite-plugin-hellajs';
import tailwindcss from '@tailwindcss/vite';
export default defineConfig({
plugins: [viteHellaJS(), tailwindcss()],
});
If you’re using TypeScript, you can add type definitions for HellaJS. Update your tsconfig.json
.
{
"compilerOptions": {
//...
"jsx": "preserve",
"types": ["@hellajs/dom"]
//...
}
}
Finally, import Tailwind into your css file.
@import "tailwindcss";
Reactive Component
Components are simply functions that return an element and signals automatically update when their values change.
Replace the content of main.js
with the following.
import { signal } from "@hellajs/core";
import { mount } from "@hellajs/dom";
import './style.css';
const Counter = () => {
const count = signal(0);
return (
<div class="p-4 text-center">
<h1 class="text-2xl mb-4">Counter: {count}</h1>
<button
onClick={() => count(count() + 1)}
class="px-4 py-2 bg-blue-500 text-white rounded"
>
Click me!
</button>
</div>
);
};
mount(Counter, '#app');
Start the development server with npm run dev
to see the app in action.
Code Explanation
signal(0)
- Creates a reactive state with an intial value of 0{count}
- Creates a reactive binding that updates automaticallyonClick={() => count(count() + 1)}
- Updates the signal valuemount
(Counter, '#app')
- Renders the component into the DOM
Derived State
What if you want to show information that depends on the count? We can use derived functions.
import { signal } from "@hellajs/core";
import { mount } from "@hellajs/dom";
import "./style.css";
const Counter = () => {
const count = signal(0);
const isEven = () => count() % 2 === 0;
const message = () =>
count() === 0 ? "Click to start!" :
isEven() ? `${count()} is even` : `${count()} is odd`;
return (
<div class="p-4 text-center">
<h1 class="text-2xl mb-4">Counter: {count}</h1>
<p class={`mb-4 ${isEven() ? 'text-green-600' : 'text-blue-600'}`}>
{message}
</p>
<button
onClick={() => count(count() + 1)}
class="px-4 py-2 bg-blue-500 text-white rounded"
>
+
</button>
</div>
);
};
mount(Counter, '#app');
Code Explanation
() => count() % 2 === 0
- Creates a derived value that updates whencount
changes- Dependency tracking -
message
depends on bothcount
andisEven
, so it updates when either changes - Reactive styling - The dyanmic class changes automatically based on
isEven()
You don’t need computed for simple derived state. You can use regular functions or inline expressions.
Conditional Controls
Let’s make our counter more interactive by adding multiple ways to change the count.
Add increment, decrement, and reset buttons, plus some smart button disabling.
import { signal, computed } from "@hellajs/core";
import { mount } from "@hellajs/dom";
import "./global.css";
const Counter = () => {
const count = signal(0);
const isEven = () => count() % 2 === 0;
const message = () =>
count() === 0 ? "Click to start!" :
isEven() ? `${count()} is even` : `${count()} is odd`;
return (
<div class="p-4 text-center">
<h1 class="text-2xl mb-4">Counter: {count}</h1>
<p class={`mb-4 ${isEven() ? 'text-green-600' : 'text-blue-600'}`}>
{message}
</p>
<div class="flex gap-2 justify-center">
<button
onClick={() => count(count() - 1)}
class="px-3 py-2 bg-red-500 text-white rounded"
disabled={count() === 0}
>
-
</button>
<button
onClick={() => count(count() + 1)}
class="px-3 py-2 bg-blue-500 text-white rounded"
>
+
</button>
<button
onClick={() => count(0)}
class="px-3 py-2 bg-gray-500 text-white rounded"
disabled={count() === 0}
>
Reset
</button>
</div>
</div>
);
};
mount(Counter, '#app');
Code Explanation
- Event handler functions - Simple functions that update state
- Conditional attributes -
disabled={count() === 0}
disables buttons reactively
Signal Side Effects
What about actions that happen when state changes? Like updating the browser tab title or saving to localStorage?
Effects run side effects automatically when their dependencies change
import { signal, computed, effect } from "@hellajs/core";
import { mount } from "@hellajs/dom";
import "./global.css";
const Counter = () => {
const count = signal(0);
const isEven = () => count() % 2 === 0;
const message = () =>
count() === 0 ? "Click to start!" :
isEven() ? `${count()} is even` : `${count()} is odd`;
// Side effect: update document title
effect(() => {
document.title = `Counter: ${count()}`;
});
return (
<div class="p-4 text-center">
<h1 class="text-2xl mb-4">Counter: {count}</h1>
<p class={`mb-4 ${isEven() ? 'text-green-600' : 'text-blue-600'}`}>
{message}
</p>
<div class="flex gap-2 justify-center">
<button
onClick={() => count(count() - 1)}
class="px-3 py-2 bg-red-500 text-white rounded"
disabled={count() === 0}
>
-
</button>
<button
onClick={() => count(count() + 1)}
class="px-3 py-2 bg-blue-500 text-white rounded"
>
+
</button>
<button
onClick={() => count(0)}
class="px-3 py-2 bg-gray-500 text-white rounded"
disabled={count() === 0}
>
Reset
</button>
</div>
</div>
);
};
mount(Counter, '#app');
Code Explanation
effect(() => { document.title = ... })
- Side effect that runs whencount
changes
Next Steps
Ready to level up? Here are your best next steps.
- Build a Todo App - Apply these skills to a more complex app
- Understanding Reactivity - Deep dive into how the reactive system works
- Components Guide - Learn to build reusable, composable components
The patterns you learned here are the foundation for any HellaJS application. You’re ready to build amazing reactive web apps!