beginner

Building a Counter App

Learn the basics by building a simple counter application

Building a Counter App

In this guide, you'll build a simple counter application to learn the fundamentals of SemaJSX.

What You'll Learn

  • Creating signals for reactive state
  • Handling user events
  • Rendering dynamic content

Prerequisites

  • Basic knowledge of JavaScript/TypeScript
  • Node.js or Bun installed
  • A text editor

Step 1: Setup

First, create a new project and install SemaJSX:

mkdir my-counter-app
cd my-counter-app
bun init -y
bun add semajsx

Step 2: Create the Counter Component

Create a file called counter.tsx:

/** @jsxImportSource semajsx/dom */

import { signal } from "semajsx/signal";
import { render } from "semajsx/dom";

function Counter() {
  // Create a reactive signal
  const count = signal(0);

  return (
    <div style={{ padding: "20px", fontFamily: "sans-serif" }}>
      <h1>Counter App</h1>
      <p style={{ fontSize: "32px", fontWeight: "bold" }}>Count: {count}</p>
      <div style={{ display: "flex", gap: "10px" }}>
        <button onClick={() => count.value--}>Decrement</button>
        <button onClick={() => (count.value = 0)}>Reset</button>
        <button onClick={() => count.value++}>Increment</button>
      </div>
    </div>
  );
}

// Render to the DOM
render(<Counter />, document.getElementById("app")!);

Step 3: Add HTML

Create an index.html file:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Counter App</title>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="./counter.tsx"></script>
  </body>
</html>

Step 4: Run the App

Using Vite for development:

bun add -d vite
bunx vite

Open your browser to http://localhost:5173 and you'll see your counter app!

It works!

Click the buttons to see the count update in real-time. Notice how only the count value updates - no full page re-render!

How It Works

Signals

const count = signal(0);

This creates a reactive signal with an initial value of 0. Signals automatically track where they're used and update those locations when the value changes.

Event Handlers

<button onClick={() => count.value++}>Increment</button>

Event handlers are just JavaScript functions. Update the signal's .value property to trigger a re-render.

Automatic Subscriptions

<p>Count: {count}</p>

When you use a signal in JSX, SemaJSX automatically subscribes to changes. When count.value changes, only this text node updates - not the entire component!

Adding Features

Step Counter

Let's add a feature to increment by a custom step:

function Counter() {
  const count = signal(0);
  const step = signal(1);

  return (
    <div>
      <h1>Counter: {count}</h1>

      <label>
        Step:
        <input
          type="number"
          value={step}
          onInput={(e) => (step.value = parseInt(e.currentTarget.value))}
        />
      </label>

      <button onClick={() => (count.value -= step.value)}>-{step}</button>
      <button onClick={() => (count.value = 0)}>Reset</button>
      <button onClick={() => (count.value += step.value)}>+{step}</button>
    </div>
  );
}

Computed Values

Add a computed signal for the doubled value:

import { signal, computed } from "semajsx/signal";

function Counter() {
  const count = signal(0);
  // Computed requires explicit dependency array
  const doubled = computed([count], (c) => c * 2);

  return (
    <div>
      <p>Count: {count}</p>
      <p>Doubled: {doubled}</p>
      <button onClick={() => count.value++}>Increment</button>
    </div>
  );
}

Next Steps

💡Challenge

Try adding a history feature that tracks all previous values!