# Your First LiveView

Build a live counter — no page refreshes, no JavaScript to write.

## What You'll Build

A counter with increment/decrement buttons that updates instantly via WebSocket. The entire feature is Python.

## Before you start

Complete [Installation](./installation.md) first. Run commands below from
the directory containing `manage.py`, with the project environment activated
(or prefix them with `.venv/bin/` or `uv run`).

The manual installation path already creates and registers `myapp`. If you
used `djust new myproject`, the generated starter app is named `myproject`;
create a separate app for this tutorial:

```bash
python manage.py startapp myapp
```

Add `"myapp"` to `INSTALLED_APPS` in your project's settings, and add
`"myapp.views"` to `LIVEVIEW_ALLOWED_MODULES` if your settings define it
(`djust init` does not; without the setting, installed apps are allowed). Keep the existing
entries. The URL step below connects this app to the site.

## 1. Create the View

Create `myapp/views.py`:

```python
from djust import LiveView
from djust.decorators import event_handler


class CounterView(LiveView):
    template_name = "myapp/counter.html"

    def mount(self, request, **kwargs):
        """Initialize state here. Runs on the first HTTP render and again when the WebSocket connects."""
        self.count = 0

    def get_context_data(self, **kwargs):
        """Return template context. Called before every render."""
        return {"count": self.count}

    @event_handler()
    def increment(self, **kwargs):
        self.count += 1

    @event_handler()
    def decrement(self, **kwargs):
        self.count -= 1
```

**Key rules:**

- `mount()` runs on the initial HTTP render and again when the WebSocket connects (unless state is restored) — set initial state here, not in `__init__`, and keep it idempotent (no one-time side effects)
- Every event handler needs `@event_handler()` — djust blocks undecorated methods for security
- Always accept `**kwargs` in event handlers (djust may pass extra metadata)
- State lives on `self` — any change to `self.count` triggers a re-render automatically

## 2. Create the Template

Create `myapp/templates/myapp/counter.html`:

```html
<!DOCTYPE html>
<html>
<head>
    <title>Counter</title>
    {% load live_tags %}
    {% djust_client_config %}
</head>
<body>
    <div dj-root>
        <h1>Count: {{ count }}</h1>

        <button dj-click="decrement">-</button>
        <button dj-click="increment">+</button>
    </div>
</body>
</html>
```

**Template requirements:**

- `{% load live_tags %}` and `{% djust_client_config %}` emit client config meta tags; djust auto-injects the client JS (~61 KB gz) into every LiveView response
- `dj-root` marks the reactive region — only this subtree is patched on updates. It is the only root attribute you write; djust stamps `dj-view` onto it server-side with the dotted path of the view rendering the page, which is what connects the page to the WebSocket session
- (Write `dj-view="myapp.views.MyView"` yourself only to name a specific view — an embedded or sticky view, or a template shared by several views. It is a literal path; there is no `dj_view_id` variable)
- `dj-click="increment"` binds a click event to the `increment` handler

## 3. Add a URL

In `myapp/urls.py`:

```python
from django.urls import path
from myapp.views import CounterView

urlpatterns = [
    path("counter/", CounterView.as_view(), name="counter"),
]
```

In the project's `myproject/urls.py`, import `include` and add the app's
routes to the existing `urlpatterns` if they are not already included:

```python
from django.urls import include, path

urlpatterns += [path("", include("myapp.urls"))]
```

A scaffolded project can keep its starter homepage. The new counter is at
`/counter/`.

## 4. Run It

```bash
python -m uvicorn myproject.asgi:application --reload
```

Visit **http://localhost:8000/counter/** and click the buttons — the count updates instantly without a page reload.

## How It Works

1. The first request is a normal HTTP response (good for SEO and initial load)
2. The page JS opens a WebSocket connection to `/ws/live/`
3. When you click a button, the client sends `{"type": "event", "event": "increment", "params": {}}` over the WebSocket
4. djust calls your `increment()` method, re-renders the template in Rust, diffs the VDOM, and sends only the changed HTML fragments back
5. The client patches the DOM — no full page reload

## Responding to Input

For text inputs, use `dj-input` (fires on the `input` event, debounced 300 ms by default for text-like fields; override with `dj-debounce`/`dj-throttle`) or `dj-change` (fires on blur):

```python
@event_handler()
def search(self, value: str = "", **kwargs):
    """The 'value' parameter receives the current input value."""
    self.query = value
```

```html
<input type="text" dj-input="search" value="{{ query }}" placeholder="Search..." />
<p>You searched for: {{ query }}</p>
```

## Passing Data from the DOM

Use `data-*` attributes to pass data to event handlers:

```python
@event_handler()
def delete_item(self, item_id: int = 0, **kwargs):
    """data-item-id="5" becomes item_id=5 (auto-converted to int)."""
    self.items = [i for i in self.items if i["id"] != item_id]
```

```html
{% for item in items %}
<li>
    {{ item.name }}
    <button dj-click="delete_item" data-item-id="{{ item.id }}">Delete</button>
</li>
{% endfor %}
```

## Next Steps

- [Core Concepts](./core-concepts.md) — understand the lifecycle and state model
- [Forms](../forms/index.md) — real-time form validation
- [State Management](../state/index.md) — debouncing, loading states, optimistic updates

### Try a real-world walkthrough

Once the counter is working, the four end-to-end tutorials apply the
same primitives to common shipping patterns:

- [Build a search-as-you-type feature](../guides/tutorial-search-as-you-type.md) — debounced single-input
- [Build a real-time comment thread](../guides/tutorial-real-time-comments.md) — multi-user broadcast
- [Build a multi-step form wizard](../guides/tutorial-multi-step-wizard.md) — stateful step cursor
- [Build a typeahead with @server_function](../guides/tutorial-typeahead-server-function.md) — partial-page server RPC
